Skip to content

Commit 6025951

Browse files
committed
Add Dancing Links (DLX) algorithm for Exact Cover problem
1 parent a71618f commit 6025951

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

other/dancing_links.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Dancing Links (DLX) Algorithm for Exact Cover Problem
3+
4+
Author: fab-c14
5+
Reference: Donald Knuth, "Dancing Links" (Algorithm X)
6+
Wikipedia: https://en.wikipedia.org/wiki/Dancing_Links
7+
8+
DLX is an efficient algorithm for solving the Exact Cover problem, such as
9+
tiling, polyomino puzzles, or Sudoku.
10+
11+
This implementation demonstrates DLX for a small exact cover problem.
12+
13+
Usage Example:
14+
>>> universe = [1, 2, 3, 4, 5, 6, 7]
15+
>>> subsets = [
16+
... [1, 4, 7],
17+
... [1, 4],
18+
... [4, 5, 7],
19+
... [3, 5, 6],
20+
... [2, 3, 6, 7],
21+
... [2, 7]
22+
... ]
23+
>>> for solution in dlx(universe, subsets):
24+
... print(solution)
25+
[0, 3, 4]
26+
[1, 2, 5]
27+
"""
28+
29+
from collections.abc import Iterator
30+
31+
32+
def dlx(universe: list[int], subsets: list[list[int]]) -> Iterator[list[int]]:
33+
"""Yields solutions to the Exact Cover problem using Algorithm X (Dancing Links)."""
34+
cover: dict[int, set[int]] = {u: set() for u in universe}
35+
for idx, subset in enumerate(subsets):
36+
for elem in subset:
37+
cover[elem].add(idx)
38+
partial: list[int] = []
39+
40+
def search() -> Iterator[list[int]]:
41+
if not cover:
42+
yield list(partial)
43+
return
44+
# Choose column with fewest rows (heuristic)
45+
c = min(cover, key=lambda col: len(cover[col]))
46+
for r in list(cover[c]):
47+
partial.append(r)
48+
removed: dict[int, set[int]] = {}
49+
for j in subsets[r]:
50+
for i in cover[j].copy():
51+
for k in subsets[i]:
52+
if k == j:
53+
continue
54+
if k in cover:
55+
cover[k].discard(i)
56+
removed[j] = cover.pop(j)
57+
yield from search()
58+
# Backtrack
59+
for j, s in removed.items():
60+
cover[j] = s
61+
for i in cover[j]:
62+
for k in subsets[i]:
63+
if k != j and k in cover:
64+
cover[k].add(i)
65+
partial.pop()
66+
67+
yield from search()
68+
69+
70+
if __name__ == "__main__":
71+
# Example: Solve the cover problem from Knuth's original paper
72+
universe = [1, 2, 3, 4, 5, 6, 7]
73+
subsets = [
74+
[1, 4, 7],
75+
[1, 4],
76+
[4, 5, 7],
77+
[3, 5, 6],
78+
[2, 3, 6, 7],
79+
[2, 7],
80+
]
81+
for solution in dlx(universe, subsets):
82+
print("Solution:", solution)

0 commit comments

Comments
 (0)