-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDLX.py
168 lines (139 loc) · 4.96 KB
/
DLX.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from abc import ABC, abstractmethod
class DLX:
class RowSupplier(ABC):
@abstractmethod
def isColumnOccupied(self, col) -> bool:
pass
@staticmethod
def solve(row_info, num_columns):
dlx = DLXImpl(row_info, num_columns)
return dlx.search(None)
@staticmethod
def solveAll(row_info, num_columns):
solutions = []
dlx = DLXImpl(row_info, num_columns)
dlx.search(solutions)
return solutions
class DLXImpl:
class Cell:
def __init__(self):
self.up = None
self.down = None
self.column = None
self.row = None
self.detached = False
class Header:
def __init__(self):
self.left = None
self.right = None
self.root = self.Cell()
self.root.up = self.root
self.root.down = self.root
self.count = 0
class Cell:
def __init__(self):
self.up = None
self.down = None
self.column = None
self.row = None
self.detached = False
class Row:
def __init__(self, info):
self.cells = []
self.info = info
def __init__(self, row_info, num_columns):
self.createHeaders(num_columns)
self.createRows(row_info, num_columns)
def search(self, solutions):
partial_solution = []
return self._search(self.column_list_, partial_solution, solutions)
def createRows(self, row_info, num_columns):
self.rows = []
for info in row_info:
row = DLXImpl.Row(info)
for c in range(num_columns):
if info.isColumnOccupied(c):
cell = DLXImpl.Cell()
cell.row = row
cell.column = self.headers[c]
cell.up = cell.column.root.up
cell.down = cell.column.root
cell.column.root.up.down = cell
cell.column.root.up = cell
cell.column.count += 1
cell.detached = False
row.cells.append(cell)
self.rows.append(row)
def createHeaders(self, num_columns):
self.column_list_ = DLXImpl.Header()
self.column_list_.right = self.column_list_
self.column_list_.left = self.column_list_
self.headers = []
for _ in range(num_columns):
h = DLXImpl.Header()
h.right = self.column_list_
h.left = self.column_list_.left
self.column_list_.left.right = h
self.column_list_.left = h
self.headers.append(h)
def _search(self, columns, partial_solution, all_solutions):
if self.isColumnListEmpty(columns):
return partial_solution
column = self.selectColumn(columns)
x = column.root.down
while x != column.root:
partial_solution.append(x.row.info)
for r in x.row.cells:
self.eliminateColumn(r.column)
solution = self._search(columns, partial_solution, all_solutions)
if solution is not None:
if all_solutions is not None:
all_solutions.append(list(solution))
else:
return solution
for r in reversed(x.row.cells):
self.reinstateColumn(r.column)
partial_solution.pop()
x = x.down
return None
def isColumnListEmpty(self, column_list):
return column_list.right == column_list
def selectColumn(self, column_list):
if self.isColumnListEmpty(column_list):
return None
min_col = column_list.right
col = min_col.right
while col != column_list:
if col.count < min_col.count:
min_col = col
col = col.right
return min_col
def eliminateColumn(self, column):
r = column.root.down
while r != column.root:
self.eliminateRow(r.row, column)
r = r.down
column.left.right = column.right
column.right.left = column.left
def reinstateColumn(self, column):
r = column.root.up
while r != column.root:
self.reinstateRow(r.row)
r = r.up
column.left.right = column
column.right.left = column
def eliminateRow(self, row, skip_column):
for cell in row.cells:
if cell.column != skip_column and not cell.detached:
cell.up.down = cell.down
cell.down.up = cell.up
assert cell.column.count > 0
cell.column.count -= 1
cell.detached = True
def reinstateRow(self, row):
for cell in reversed(row.cells):
if cell.detached:
cell.up.down = cell
cell.down.up = cell
cell.column.count += 1
cell.detached = False