↫ Home

Encoding Hitori in Answer Set Programming

Hitori is logic puzzle with goal to black out some cells such that

ASP (Answer Set Programming) is declarative approach to problem solving. Idea is to give description of problem you are solving instead of giving algorithm (how to solve). ASP system will take that description and figure out solution using approach similar SAT solvers.

There are four kinds of ASP declarations we will be using.

Facts - These are statements of form square(16), color(apple, red) for example. They represent ground facts which we know are true - 16 is a square, color of apple is red.

Rules allow us to derive more facts and are of form A :- B, C, D. prime(N) :- not composite(N), grandparent(A, C) :- parent(A, B), parent(B, C) are examples of rule. First rule allows us to derive 17 is prime if we know that 17 is not composite. Second rule encodes that if A is parent of B and B is parent of C then A is grandparent of C. If parent(joe, smith) and parent(smith, ricky) then it allow us to derive grandparent(joe, ricky).

Using constraints, we can reject some solutions - we are not interested in. Constraints are of form :- B, C, D. Semantics of constraints in ASP is it will reject solutions where all of B, C, D are true. For example :- prime(N), even(N) will ignore even primes from solution.

Finally directives are statements starting with #. They allow constants to be declared using #const n = 4, output to be controlled using #show prime/1.

First step to solve Hitori is declaring no number appears twice in row or column in solution.

1 { blacked(X, Y1); blacked(X, Y2) } 2 :- written(X, Y1, N), written(X, Y2, N), Y1 < Y2.
1 { blacked(X1, Y); blacked(X2, Y) } 2 :- written(X1, Y, N), written(X2, Y, N), X1 < X2.

Body part (part after :-) is enabled when same number is written in same row (first rule) / same column (second rule) twice. Head part (part before :-) states that one of occurrence should be blacked out or both. This is achieved using choice rule. Choice rule is of form { A; B; C } which states that solver has choice to include any subset of this set as derived fact. If choice rule contains numbers before and after set then size of subset should be greater or equal to before number and less than or equal to after number. { A; B; C } = N means N { A; B; C } N.

Blacked out cells are not neighbors can be expressed using following constraints.

:- blacked(X, Y), blacked(X + 1, Y).
:- blacked(X, Y), blacked(X, Y + 1).

Finally to guarantee non blacked out cells are part of single connected component, we select one of non blacked out cell and start doing breadth first search - adding discovered cells in reachable relation. All non blacked out cells should be reachable.

{ start(X, Y) : written(X, Y, _), not blacked(X, Y) } = 1.
reachable(X, Y) :- start(X, Y).
reachable(X + 1, Y) :- reachable(X, Y), written(X + 1, Y, _), not blacked(X + 1, Y).
reachable(X, Y + 1) :- reachable(X, Y), written(X, Y + 1, _), not blacked(X, Y + 1).
reachable(X - 1, Y) :- reachable(X, Y), written(X - 1, Y, _), not blacked(X - 1, Y).
reachable(X, Y - 1) :- reachable(X, Y), written(X, Y - 1, _), not blacked(X, Y - 1).

:- written(X, Y, _), not blacked(X, Y), not reachable(X, Y).

Finally we add #show blacked/2 to display blacked cells. Let’s now try out our encoding on instance of Hitori which is shown below.

Running below program using clingo outputs which cells need to colored black (shown above). written(X, Y, N) relation means there is number N written at row X and column Y.

written(1, 1, 1). written(1, 2, 1). written(1, 3, 2).
written(1, 4, 4). written(1, 5, 3). written(1, 6, 5).
written(2, 1, 1). written(2, 2, 1). written(2, 3, 5).
written(2, 4, 4). written(2, 5, 4). written(2, 6, 6).
written(3, 1, 4). written(3, 2, 6). written(3, 3, 6).
written(3, 4, 2). written(3, 5, 1). written(3, 6, 1).
written(4, 1, 6). written(4, 2, 3). written(4, 3, 3).
written(4, 4, 3). written(4, 5, 5). written(4, 6, 4).
written(5, 1, 2). written(5, 2, 3). written(5, 3, 4).
written(5, 4, 1). written(5, 5, 6). written(5, 6, 5).
written(6, 1, 2). written(6, 2, 5). written(6, 3, 4).
written(6, 4, 6). written(6, 5, 2). written(6, 6, 5).

1 { blacked(X, Y1); blacked(X, Y2) } 2 :- written(X, Y1, N), written(X, Y2, N), Y1 < Y2.
1 { blacked(X1, Y); blacked(X2, Y) } 2 :- written(X1, Y, N), written(X2, Y, N), X1 < X2.

:- blacked(X, Y), blacked(X + 1, Y).
:- blacked(X, Y), blacked(X, Y + 1).

{ start(X, Y) : written(X, Y, _), not blacked(X, Y) } = 1.
reachable(X, Y) :- start(X, Y).
reachable(X + 1, Y) :- reachable(X, Y), written(X + 1, Y, _), not blacked(X + 1, Y).
reachable(X, Y + 1) :- reachable(X, Y), written(X, Y + 1, _), not blacked(X, Y + 1).
reachable(X - 1, Y) :- reachable(X, Y), written(X - 1, Y, _), not blacked(X - 1, Y).
reachable(X, Y - 1) :- reachable(X, Y), written(X, Y - 1, _), not blacked(X, Y - 1).

:- written(X, Y, _), not blacked(X, Y), not reachable(X, Y).

#show blacked/2.