Chapter 15
Hitori
Hitori is the puzzle of hiding duplicates. The grid is a square of digits; the solver must shade some cells black and leave the rest unshaded, subject to three rules. Every row and every column must contain each digit at most once among its unshaded cells. No two shaded cells may touch along an edge. And every unshaded cell must reach every other unshaded cell through a chain of orthogonally adjacent unshaded cells, i.e. the unshaded region must be connected.
The puzzle was published by Nikoli in ; the Japanese name, Hitori ni shite kure, literally “leave me alone,” is a pun on the solving procedure of eliminating duplicates by shading. The puzzle has the unusual property, among Nikoli shadings, of operating on a complement region: the unshaded cells carry the content and must be connected, while the shaded cells are merely the deletion pattern.
Each of the three rules contributes a distinct CP-SAT constraint register. The row and column uniqueness is a “pairwise conflict” family: for each pair of cells sharing a row or column and carrying the same digit, at least one must be shaded. The adjacency rule is a pairwise constraint on neighbouring cells. The connectivity rule is, as in Nurikabe, a distance-from-root flow but applied to the complement of the shading.
Rules and a small instance
A Hitori puzzle is an grid (usually with , and typically ). Each cell carries a positive integer. The solver chooses a subset of cells to shade subject to three rules.
In every row, each digit appears at most once among the unshaded cells; likewise in every column.
No two shaded cells are orthogonally adjacent.
The unshaded cells form a single orthogonally connected region.
Figure 15.1 is puzzle from the janko.at Hitori archive, an instance by Otto Janko.
The programming model
For each cell define a Boolean , with meaning shaded.
Pairwise uniqueness on unshaded cells
For each row and each pair of distinct columns with grid digits equal, at least one of the two cells must be shaded: The analogous constraint holds for every column and every pair of rows with equal column digits.
No adjacent black cells
For each pair of orthogonally adjacent cells ,
Connectivity of the unshaded region
Attach a distance variable to every cell, and a Boolean marking a designated unshaded root. The root’s is and implies the cell is unshaded. Every other unshaded cell must have and an orthogonally adjacent unshaded neighbour with .
Exactly one is set: .
A solver in fifty lines
from ortools.sat.python import cp_model
def solve_hitori(R, C, grid):
"""grid[r][c] = digit. Returns shaded {0, 1} grid."""
m = cp_model.CpModel()
s = {(r, c): m.NewBoolVar("")
for r in range(R) for c in range(C)}
for r in range(R):
for c1 in range(C):
for c2 in range(c1+1, C):
if grid[r][c1] == grid[r][c2]:
m.Add(s[(r, c1)] + s[(r, c2)] >= 1)
for c in range(C):
for r1 in range(R):
for r2 in range(r1+1, R):
if grid[r1][c] == grid[r2][c]:
m.Add(s[(r1, c)] + s[(r2, c)] >= 1)
for r in range(R):
for c in range(C):
for dr, dc in [(0,1),(1,0)]:
nr, nc = r+dr, c+dc
if 0 <= nr < R and 0 <= nc < C:
m.Add(s[(r,c)] + s[(nr,nc)] <= 1)
d = {(r, c): m.NewIntVar(0, R*C-1, "")
for r in range(R) for c in range(C)}
rho = {(r, c): m.NewBoolVar("")
for r in range(R) for c in range(C)}
un = {k: m.NewBoolVar("") for k in s}
for k in s:
m.Add(s[k] == 0).OnlyEnforceIf(un[k])
m.Add(s[k] == 1).OnlyEnforceIf(un[k].Not())
m.Add(d[k] == 0).OnlyEnforceIf(rho[k])
m.Add(un[k] == 1).OnlyEnforceIf(rho[k])
m.Add(sum(rho.values()) == 1)
def less(p, q):
b = m.NewBoolVar("")
m.Add(d[q] + 1 == d[p]).OnlyEnforceIf(b)
m.Add(d[q] + 1 != d[p]).OnlyEnforceIf(b.Not())
return b
for r in range(R):
for c in range(C):
cell = (r, c); sr = rho[cell]
m.Add(d[cell] >= 1).OnlyEnforceIf([un[cell],
sr.Not()])
preds = []
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if not (0 <= nr < R and 0 <= nc < C):
continue
nb = (nr, nc)
lb = less(cell, nb)
p = m.NewBoolVar("")
m.AddBoolAnd([un[nb], lb]).OnlyEnforceIf(p)
m.AddBoolOr([un[nb].Not(), lb.Not()]
).OnlyEnforceIf(p.Not())
preds.append(p)
if preds:
m.AddBoolOr(preds).OnlyEnforceIf(
[un[cell], sr.Not()])
sol = cp_model.CpSolver()
sol.Solve(m)
return [[sol.Value(s[(r, c)])
for c in range(C)] for r in range(R)]
The toy of Figure 15.1 solves uniquely in about milliseconds.
A hard instance
Figure 15.3 is puzzle from the janko.at Hitori archive, a instance by Koyoppz, the only puzzle in the archive at the top-tier difficulty “schwer”. The grid carries cells filled with digits to , many of them with multiple repetitions; the solver’s task is to find the unique shading pattern that satisfies all three rules. CP-SAT returns the result in about seconds.
Sources. Hitori was first published by Nikoli in . The toy is puzzle from the janko.at Hitori archive (Otto Janko); the hard instance is puzzle (Koyoppz). Hearn and Demaine () proved the general Hitori decision problem NP-complete via a reduction from planar -SAT in Games, Puzzles and Computation (AK Peters).