Hacker News new | ask | show | jobs
by Marc66FR 3312 days ago
I never bothered creating a program to solve this as I prefer to do the exercise mentally. I've been playing with 1440 puzzles: one for each display of a 24 hour clock. The puzzle is simple: take a displayed time and using the 4 displayed numbers and the 4 basic operations (+ - x /), make it solve to zero. To make it more complex, it is forbidden to multiply by zero. Eg. 12:34 >> (1+4)-(2+3)=0. The puzzle does not solve for all hours, but it does for 12:3x and many other times
1 comments

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.
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.