Hacker News new | ask | show | jobs
by zmonx 3318 days ago
That's a neat puzzle! Here is the Prolog code I posted earlier, extended and adapted to solve such tasks:

First, let us again start with a suitable operator definition, so that we can use × as an infix operator in expressions:

    :- op(400, yfx, ×).
Next, let us declaratively describe what we mean by a "clock", using integer constraints:

    clock(Vs) :-
            Vs = [H0,H1,M1,M2],
            Vs ins 0..9,
            H0*10 + H1 #< 24,
            M1*10 + M2 #< 60,
            label(Vs).
For example:

    ?- clock(Vs).
    Vs = [0, 0, 0, 0] ;
    Vs = [0, 0, 0, 1] ;
    Vs = [0, 0, 0, 2] ;
    Vs = [0, 0, 0, 3] .
We can collect all solutions as a quick verification:

    ?- findall(., clock(Vs), Ls), length(Ls, L).
    Ls = ['.', '.', '.', '.', '.', '.', '.', '.', '.'|...],
    L = 1440.
This confirms that there are 1440 puzzles of this kind, one for each possible clock time.

The puzzle now asks for expressions that evaluate to 0. We can relate any given clock display to an expression like this:

    clock_solution(Vs, Expr) :-
            clock(Vs),
            numbers_tree(Vs, T),
            tree_op_expr_value(T, _, Expr, 0).
This reuses predicates I posted earlier, which I only need to slightly modify to prohibit multiplication by zero, and which I repeat here for completeness:

    numbers_tree([N], leaf(N)).
    numbers_tree(Vs0, binary(Left, Right)) :-
            permutation(Vs0, Vs),
            append([L|Ls], [R|Rs], Vs),
            numbers_tree([L|Ls], Left),
            numbers_tree([R|Rs], Right).

    tree_op_expr_value(leaf(N), _, N, N).
    tree_op_expr_value(binary(Left0, Right0), Op, Expr, Value) :-
            tree_op_expr_value(Left0, _, Left, VL),
            tree_op_expr_value(Right0, _, Right, VR),
            op_values_value(Op, VL, VR, Value),
            Expr =.. [Op,Left,Right].

    op_values_value(+, A, B, V) :- V is A + B.
    op_values_value(-, A, B, V) :- V is A - B.
    op_values_value(×, A, B, V) :- A =\= 0, B =\= 0, V is A * B.
    op_values_value(/, A, B, V) :- B =\= 0, V is A rdiv B.
We can use this to enumerate all times that have a solution:

    ?- clock(C),
       once(clock_solution(C, S)).
    C = [0, 0, 0, 0],
    S = 0+(0+(0+0)) ;
    C = [0, 0, 0, 1],
    S = 0/(0+(0+1)) ;
    C = [0, 0, 0, 2],
    S = 0/(0+(0+2)) ;
    C = [0, 0, 0, 3],
    S = 0/(0+(0+3)) .
And here is the list of all times that don't have a solution:

    ?- clock(C),
       \+ clock_solution(C, S).
    C = [1, 6, 4, 8] ;
    C = [1, 7, 4, 9] ;
    C = [1, 7, 5, 9] ;
    C = [1, 8, 4, 6] ;
    C = [1, 9, 4, 7] ;
    C = [1, 9, 5, 7] ;
    false.
1 comments

Hmm, I think there also needs to be a rule that you can't have a numerator of 0, otherwise any case where you have an expression a * b = 0 with a = 0 then either b=0 or a / b = 0. So the restriction that you can't multiply by 0 becomes somewhat meaningless.
The constraints I impose are sufficient to exclude such cases in multiplications: a/b is first evaluated, and if the result is 0, it is precluded from multiplications. To avoid also divisions resulting in 0, you are right: We can easily constrain the nominator to express this, completely analogously to multiplication. This goes beyond the initial requirements though.