Live data from Hacker News

Binary Puzzle

binarypuzzle.com

61–64 of 64 posts

Re: Binary Puzzle

#61
post #53
post #4

The declarative programming language Prolog is a natural choice for solving such combinatorial tasks. Here is a Prolog formulation of the puzzle, using constraint logic programming over integers that ships with typical Prolog systems: binary_puzzle(Rows) :- length(Rows, L), maplist(same_length(Rows), Rows), maplist(only_two_next_to_each_other, Rows), transpose(Rows, Cols), maplist(only_two_next_to_each_other, Cols),…

I like your Prolog solution! I thought I'd take a crack at a Python SAT-solver solution, also done in less than 1 second. from z3 import * # we use '-1' for empty instance = ((-1,-1,-1,-1,-1,1,-1,-1,-1,1), (1,-1,-1,-1,-1,-1,-1,0,-1,-1), (-1,-1,0,-1,-1,-1,-1,0,-1,-1), (-1,0,0,-1,-1,-1,0,-1,-1,1), (1,-1,-1,-1,-1,-1,-1,-1,-1,1), (-1,-1,-1,0,-1,-1,1,-1,-1,-1), (0,-1,-1,-1,-1,1,-1,-1,-1,-1), (-1,-1,-1,-1,-1,-1,-1,0,-1,0),…

Very nice! I see you are using a way to express the constrant "at most two cells of the same value in direct succession in each row and column " that is shorter than the formulation I have posted.

I used a conjunction of two constraints, for each triple of cells A, B, C in direct succession:

    ( A #= B ) #==> ( C #\= B ),
    ( B #= C ) #==> ( B #\= A )
And you are using only one constraint:

    B #= A #==> C #\= A
To the SAT purist, the question may now arise: Are these ways really equivalent, do they state the same constraint?

One way to check it is to apply CLP(B): Constraint Logic Programming over Boolean variables, which ships for example in SICStus Prolog and is provided as library(clpb).

Using this library, we can ask:

    ?- taut((((A =:= B) =
This asks whether it is a tautology (taut/2) that the two ways to express the constraint are equivalent.

The system answers:

    T = 1,
    sat(A=:=A),
    sat(B=:=B),
    sat(C=:=C).
So: Yes, the two ways are in fact equivalent, because this equivalence always holds. Yours being shorter, I would in fact prefer it and could also use it in the Prolog solution! Well done!

One additional neat thing about the Prolog solution: Since no further solution is found on backtracking in this case, we have in fact also verified that the reported solution is indeed unique. I suppose this check could also be added to your solution somehow to make such observations possible?

For example, suppose I modify the puzzle to read:

    puzzle([[_,_,_,_,_,_,_,_,_,1],
            [1,_,_,_,_,_,_,0,_,_],
            [_,_,0,_,_,_,_,0,_,_],
            [_,0,0,_,_,_,0,_,_,1],
            [1,_,_,_,_,_,_,_,_,1],
            [_,_,_,0,_,_,1,_,_,_],
            [0,_,_,_,_,1,_,_,_,_],
            [_,_,_,_,_,_,_,0,_,0],
            [0,_,_,_,_,_,_,_,_,0],
            [_,0,_,0,_,1,_,_,_,_]]).
Then there are 144 solutions (I have removed the first "1" from the first row), which I can generate with the Prolog solution within a second.

Re: Binary Puzzle

#62
post #25

Earlier quoted context omitted.

http://0hh1.com is a much better place to learn the rules.

Indeed it is, it explains the rule, gives you feedback, and it better looking in general.

I find it's easier than the puzzle linked since my brain can't unsee '1' and '0' as one and zero, whereas with the blocks, they're just abstract shapes with no meaning aside from colour.

Re: Binary Puzzle

#63
post #39

Earlier quoted context omitted.

As eriknstr correctly points out, the puzzle I have included above is already classified as "very hard". SICStus Prolog 4.3.2 takes about 20 milliseconds to find the solution with the formulation I posted. I tested this on a machine where cat /dev/cpuinfo yields: vendor_id : GenuineIntel cpu family : 6 model : 26 model name : Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz stepping : 5 microcode : 0x11 cpu MHz : 2668.000 cach…

Did you try graphing how runtime scales as a function of puzzle size? (Assuming there are puzzles available of large sizes.) It would also be cool if your program could be used to generate new puzzles?

You can readily use the Prolog code I posted to generate such puzzles.

For example, you can go about it as follows. First, let us define the following relation that yields the N-th solution of a given Prolog goal:

    call_nth(Goal_0, C) :-
       State = count(0,_),
       Goal_0,
       arg(1, State, C1),
       C2 #= C1+1,
       nb_setarg(1, State, C2),
       C = C2.
We can use this as a building block to determine whether a given puzzle is still well-defined in the sense that it only admits a single solution. If call_nth(Goal, 2) for a given Goal succeeds, then we know that there are at least 2 answers, which is what we want to avoid in this case.

So, I define single_solution/1 to be true iff there are not two solutions:

    single_solution(Rows) :-
            \+ ( binary_puzzle(Rows),
                   append(Rows, Vs),
                   call_nth(label(Vs), 2) ).
As a further building block, consider the definition of generalize_row/2, which relates a given roster with hints to a generalized version where at most one hint has been removed:

    generalize_row([], []).
    generalize_row([Row|Rows0], [Row|Rows]) :-
            generalize_row(Rows0, Rows).
    generalize_row([Row0|Rows], [Row|Rows]) :-
            generalize_element(Row0, Row).

    generalize_element([], []).
    generalize_element([I|Es], [_|Es]) :- integer(I).
    generalize_element([E|Es0], [E|Es]) :-
            generalize_element(Es0, Es).
For example, here are the various possible generalizations for a single row, obtained by removing at most a single hint in each case, and replacing it by a logical variable:

    ?- generalize_element([1,0,1], Ls).
    Ls = [_54540, 0, 1] ;
    Ls = [1, _54546, 1] ;
    Ls = [1, 0, _54552] ;
    Ls = [1, 0, 1].
Note how different generalizations are naturally obtained via backtracking.

With these building blacks, everything is in place to generate such puzzles, with code that could look as follows:

    generate_puzzle(N, Hints, Rows) :-
            length(Rows0, N),
            binary_puzzle(Rows0),
            append(Rows0, Vs),
            once(label(Vs)),
            Hints #= N^2 - R,
            length(Rs, R),
            foldl(remove_hints, Rs, Rows0, Rows).

    remove_hints(_, Rows0, Rows) :-
            generalize_row(Rows0, Rows),
            Rows0 \== Rows,
            single_solution(Rows).
This starts from a fully instantiated roster of size NxN, and then successively removes hints. It is a ternary relation, also yielding the number of hints that remain, and of course the list of rows that constitute the found puzzle.

Here is an example invocation for N=4:

    ?- generate_puzzle(4, H, Rows),
       maplist(portray_clause, Rows).
The first two answers are:

    [0, 0, 1, 1].
    [0, 1, 0, 1].
    [1, 0, 1, 0].
    [1, 1, 0, 0].
    H = 16,

    [0, 0, 1, 1].
    [0, 1, 0, 1].
    [1, 0, 1, 0].
    [_, 1, 0, 0].
    H = 15
In the first solution, all hints are still present, and in the second solution, exactly one hint was removed.

You can use the second argument to specify the number of hints you want for the final puzzle, which you can regard as setting a difficulty (lower makes it harder). For example, let us try to find solutions with only 10 (instead of 16 or 15) hints:

    ?- generate_puzzle(4, 10, Rows),
       maplist(portray_clause, Rows).
In that case, we find for example:

    [0, 0, 1, 1].
    [0, 1, 0, 1].
    [_, _, 1, 0].
    [_, _, _, _].
and also:

    [0, 0, 1, 1].
    [0, 1, 0, 1].
    [_, 0, _, 0].
    [_, _, _, _].

In the 10x10 puzzle I showed earlier, only 22 hints are present, making it classified as "very hard". Let us generate 10x10 puzzles that are easier by containing more hints. For example, let us generate a 10x10 puzzle with 30 hints via the query:

    ?- generate_puzzle(10, 30, Rows),
       maplist(portray_clause, Rows).
Within at most seconds, we get for example:

    [0, 0, 1, 0, 0, 1, 1, 0, 1, 1].
    [0, 0, 1, 0, 1, 0, 1, 0, 1, 1].
    [_, _, _, _, _, _, _, _, _, 0].
    [_, _, _, _, 1, _, 1, 1, _, 1].
    [_, 1, _, _, _, _, _, _, _, 1].
    [_, _, 1, _, _, _, _, 1, 1, _].
    [_, _, _, _, _, _, _, _, _, _].
    [_, _, _, _, _, _, _, _, _, _].
    [_, _, _, _, _, _, _, _, _, _].
    [_, _, _, _, _, _, _, _, _, _].
This should provide a nice basis for further experiments to automatically generate such puzzles. For example, as you can see, the puzzle has a certain shape to it, and you can generate different shapes more readily by choosing random elements for generalization, instead of going so systematically about it.

Re: Binary Puzzle

#64

Very cool! It keeps saying that it's not solved correctly, but i cannot find the mistake: http://i.imgur.com/qPJLafH.png Already ironed out identical columns but can't seem to find anything else. Any idea?

I also have a filled-in square which it says is incorrect with no indication of where/why it's incorrect, which is quite frustrating. Would it really be too easy if you could guess and then be told why it was inconsistent?
Post reply on HN