dancing-links 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/array_combs_perms.rb +59 -0
- data/lib/dancing_links.rb +442 -0
- data/test/tc_dancing_links.rb +222 -0
- metadata +39 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
# Copyright (c) 2006 Justin W Smith
|
3
|
+
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
5
|
+
# software and associated documentation files (the "Software"), to deal in the Software
|
6
|
+
# without restriction, including without limitation the rights to use, copy, modify,
|
7
|
+
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to the following
|
9
|
+
# conditions:
|
10
|
+
|
11
|
+
# The above copyright notice and this permission notice shall be included in all copies
|
12
|
+
# or substantial portions of the Software.
|
13
|
+
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
15
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
16
|
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
17
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
18
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
19
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
|
21
|
+
class Array
|
22
|
+
def to_s
|
23
|
+
s = "["
|
24
|
+
each_index do |i|
|
25
|
+
s << " " + self[i].to_s
|
26
|
+
s << ", " unless (i == self.length-1)
|
27
|
+
end
|
28
|
+
s << "]"
|
29
|
+
end
|
30
|
+
|
31
|
+
def comb_helper head, tail, &block
|
32
|
+
if tail.length == 0
|
33
|
+
block.call(head)
|
34
|
+
else
|
35
|
+
tail.each_index do |i|
|
36
|
+
t = tail.clone
|
37
|
+
t.delete_at(i)
|
38
|
+
comb_helper head.clone << tail[i], t, &block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def combinations &block
|
44
|
+
comb_helper [], self, &block
|
45
|
+
end
|
46
|
+
|
47
|
+
def permutations &block
|
48
|
+
perm_helper [self[0]], self[1...self.length], &block
|
49
|
+
end
|
50
|
+
|
51
|
+
def perm_helper head, tail, &block
|
52
|
+
block.call(head)
|
53
|
+
t = tail.clone
|
54
|
+
until t.empty?
|
55
|
+
val = t.pop
|
56
|
+
comb_helper head.clone << val, t, &block
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,442 @@
|
|
1
|
+
|
2
|
+
# Copyright (c) 2006 Justin W Smith
|
3
|
+
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
5
|
+
# software and associated documentation files (the "Software"), to deal in the Software
|
6
|
+
# without restriction, including without limitation the rights to use, copy, modify,
|
7
|
+
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to the following
|
9
|
+
# conditions:
|
10
|
+
|
11
|
+
# The above copyright notice and this permission notice shall be included in all copies
|
12
|
+
# or substantial portions of the Software.
|
13
|
+
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
15
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
16
|
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
17
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
18
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
19
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
|
21
|
+
|
22
|
+
=begin rdoc
|
23
|
+
|
24
|
+
This module provides an implementation of the "dancing links" algorhthm
|
25
|
+
to solve the "exact cover" problem.
|
26
|
+
|
27
|
+
The "exact cover" problem:
|
28
|
+
- Given a set of vectors containing 0's and 1's:
|
29
|
+
- Find a subset of these vectors that collectively contain one and only one 1 in each and every column.
|
30
|
+
|
31
|
+
For example the follwing set of vectors:
|
32
|
+
- A = [ 0, 1, 0]
|
33
|
+
- B = [ 1, 1, 0]
|
34
|
+
- C = [ 1, 0, 0]
|
35
|
+
- D = [ 0, 0, 1]
|
36
|
+
|
37
|
+
Has two solutions:
|
38
|
+
- A = [ 0, 1, 0]
|
39
|
+
- C = [ 1, 0, 0]
|
40
|
+
- D = [ 0, 0, 1]
|
41
|
+
|
42
|
+
and
|
43
|
+
- B = [ 1, 1, 0]
|
44
|
+
- D = [ 0, 0, 1]
|
45
|
+
|
46
|
+
A better description of the problem can be found here:
|
47
|
+
http://en.wikipedia.org/wiki/Exact_cover
|
48
|
+
|
49
|
+
The "dancing links" algorithm is a commonly used solution for this problem, and
|
50
|
+
was found by Donald Knuth. The algorithm involves the construction of a
|
51
|
+
sparse matrix containing nodes that are doubly-linked both horizontally and
|
52
|
+
vertically. The matrix itself simply facilitates the depth-first search
|
53
|
+
(aka backtracking) part of the algorithm.
|
54
|
+
|
55
|
+
The importance of the doubly-linked nodes are that they allow for quick
|
56
|
+
removal/restoration of rows/columns of nodes, which is exactly what a
|
57
|
+
backtracking algorithm for the "exact cover" problem needs.
|
58
|
+
|
59
|
+
horizontal removal:
|
60
|
+
- node.left.right = node.right
|
61
|
+
- node.right.left = node.left
|
62
|
+
|
63
|
+
horizontal restoration:
|
64
|
+
- node.left.right = node
|
65
|
+
- node.right.left = node
|
66
|
+
|
67
|
+
A better description of the algorithm can be found here:
|
68
|
+
http://en.wikipedia.org/wiki/Dancing_Links
|
69
|
+
|
70
|
+
=end
|
71
|
+
|
72
|
+
module DancingLinks
|
73
|
+
|
74
|
+
private
|
75
|
+
=begin rdoc
|
76
|
+
This class is used internally by the "Dancing Links" algorithm.
|
77
|
+
|
78
|
+
This "matrix" contains nodes for all of the "1"s of the matrix.
|
79
|
+
The nodes linked together in a doubly-linked chain both horizontally and vertically.
|
80
|
+
Each column in the matrix has a header node, and the column headers have a "header" called the root.
|
81
|
+
|
82
|
+
The headers, links, counters, etc... all serve to do the "book keeping" of the algorithm
|
83
|
+
so that columns/rows can quickly be removed/replaced while the search occuring.
|
84
|
+
|
85
|
+
The algorithm is essentially a depth-first search (aka backtracking) with the matrix
|
86
|
+
facilitating the neccessary operations.
|
87
|
+
|
88
|
+
=end
|
89
|
+
class SparseMatrix
|
90
|
+
|
91
|
+
=begin rdoc
|
92
|
+
All nodes within the matrix are instances of this class.
|
93
|
+
=end
|
94
|
+
class SparseMatrixNode
|
95
|
+
def initialize row_num=-1, header=self, left=self, right=self, up=self, down=self
|
96
|
+
@row_num = row_num
|
97
|
+
@header = header
|
98
|
+
@left = left
|
99
|
+
@right = right
|
100
|
+
@up = up
|
101
|
+
@down = down
|
102
|
+
|
103
|
+
#puts "row: #{row_num}"
|
104
|
+
|
105
|
+
@header.count = (@header.count + 1) if @header
|
106
|
+
@left.right = self if @left
|
107
|
+
@right.left = self if @right
|
108
|
+
@up.down = self if @up
|
109
|
+
@down.up = self if @down
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_accessor :up, :down, :left, :right, :header, :row_num
|
113
|
+
end
|
114
|
+
|
115
|
+
=begin rdoc
|
116
|
+
The class for the header nodes within the matrix. Adds two attributes that are
|
117
|
+
needed by the headers.
|
118
|
+
=end
|
119
|
+
class SparseMatrixHeader < SparseMatrixNode
|
120
|
+
def initialize index=nil, *args
|
121
|
+
super( -1, *args )
|
122
|
+
@index = index
|
123
|
+
@count = 0
|
124
|
+
end
|
125
|
+
|
126
|
+
attr_accessor :count, :index
|
127
|
+
end
|
128
|
+
|
129
|
+
=begin rdoc
|
130
|
+
The class for the root node of the matrix. Adds one attribute that is needed
|
131
|
+
by the root node.
|
132
|
+
=end
|
133
|
+
class SparseMatrixRoot < SparseMatrixNode
|
134
|
+
def initialize *args
|
135
|
+
super( -1, nil, *args )
|
136
|
+
@count = 0
|
137
|
+
end
|
138
|
+
attr_accessor :count
|
139
|
+
end
|
140
|
+
|
141
|
+
=begin rdoc
|
142
|
+
creates the root node and calls build_sparse_matrix to construct the matrix.
|
143
|
+
* matrix - an array of boolean arrays. This array represents the available rows from which the algorithm must choose.
|
144
|
+
=end
|
145
|
+
def initialize matrix
|
146
|
+
#puts "init"
|
147
|
+
@root = SparseMatrixRoot.new
|
148
|
+
build_sparse_matrix(matrix)
|
149
|
+
#puts "end-init"
|
150
|
+
end
|
151
|
+
|
152
|
+
=begin rdoc
|
153
|
+
Iterates through the matrix (an array of boolean arrays). When finding a "true"
|
154
|
+
value, it constructs a node and places the node appropriately within the matrix.
|
155
|
+
|
156
|
+
Note that any columns (from the array) which contain no "true" values will simply
|
157
|
+
be ignored. The algorithm will still attempt to find a solution for the existing
|
158
|
+
columns.
|
159
|
+
=end
|
160
|
+
def build_sparse_matrix(matrix)
|
161
|
+
matrix.each_index do |i|
|
162
|
+
row = matrix[i]
|
163
|
+
row_node = nil
|
164
|
+
row.each_index do |j|
|
165
|
+
if row[j]
|
166
|
+
header = get_column_header j
|
167
|
+
if row_node
|
168
|
+
row_node = node =
|
169
|
+
SparseMatrixNode.new( i, header, row_node, row_node.right, header.up, header)
|
170
|
+
else
|
171
|
+
node =
|
172
|
+
SparseMatrixNode.new( i, header, nil, nil, header.up, header)
|
173
|
+
row_node= node.left= node.right= node
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
=begin rdoc
|
181
|
+
A utility for build_sparse_matrix. Finds and/or constructs (if needed) the header
|
182
|
+
node for a given column.
|
183
|
+
=end
|
184
|
+
def get_column_header index
|
185
|
+
header = @root.right
|
186
|
+
|
187
|
+
while header != @root && header.index <= index
|
188
|
+
if header.index == index
|
189
|
+
return header
|
190
|
+
end
|
191
|
+
header = header.right
|
192
|
+
end
|
193
|
+
new_header = SparseMatrixHeader.new( index, @root, header.left, header)
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
attr_accessor :root
|
198
|
+
end
|
199
|
+
|
200
|
+
=begin rdoc
|
201
|
+
An internal recursive method which does the "searching" for the solution.
|
202
|
+
=end
|
203
|
+
def solve( root, selected_rows, sol_count, &block )
|
204
|
+
|
205
|
+
# terminating condition
|
206
|
+
if root.right == root
|
207
|
+
unless block == nil
|
208
|
+
block.call(selected_rows)
|
209
|
+
end
|
210
|
+
sol_count += 1
|
211
|
+
return sol_count
|
212
|
+
end
|
213
|
+
|
214
|
+
# determine direction of search (which column to proceed with)
|
215
|
+
header = min_column root
|
216
|
+
if header.count == 0
|
217
|
+
#puts header.index
|
218
|
+
return sol_count
|
219
|
+
end
|
220
|
+
|
221
|
+
node = header.down
|
222
|
+
|
223
|
+
# iterate through rows with nodes in the column
|
224
|
+
while node != header
|
225
|
+
header_stk, row_stk_stk = select_row node
|
226
|
+
sol_count = solve( root, selected_rows.push(node.row_num), sol_count, &block)
|
227
|
+
selected_rows.pop
|
228
|
+
restore_selected_row header_stk, row_stk_stk
|
229
|
+
node = node.down
|
230
|
+
end
|
231
|
+
sol_count
|
232
|
+
end
|
233
|
+
|
234
|
+
=begin rdoc
|
235
|
+
An internal utility method which modifies the "sparse matrix"
|
236
|
+
=end
|
237
|
+
def select_row row_node
|
238
|
+
row_stk_stk = Array.new
|
239
|
+
header_stk = Array.new
|
240
|
+
node = row_node
|
241
|
+
loop do
|
242
|
+
header = node.header
|
243
|
+
row_stk_stk.push( remove_rows_for_header(header) )
|
244
|
+
remove_horizontal header
|
245
|
+
|
246
|
+
header_stk.push(header)
|
247
|
+
node = node.right
|
248
|
+
break if node == row_node
|
249
|
+
end
|
250
|
+
[header_stk, row_stk_stk]
|
251
|
+
end
|
252
|
+
|
253
|
+
=begin rdoc
|
254
|
+
An internal utility method which modifies the "sparse matrix"
|
255
|
+
=end
|
256
|
+
def restore_selected_row header_stk, row_stk_stk
|
257
|
+
until header_stk.empty?
|
258
|
+
row_stk = row_stk_stk.pop
|
259
|
+
header = header_stk.pop
|
260
|
+
|
261
|
+
restore_horizontal header
|
262
|
+
restore_rows_for_header row_stk
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
=begin rdoc
|
267
|
+
An internal utility method which modifies the "sparse matrix"
|
268
|
+
=end
|
269
|
+
def remove_rows_for_header header
|
270
|
+
row_stk = Array.new
|
271
|
+
col_node = header.down
|
272
|
+
while col_node != header
|
273
|
+
node = col_node
|
274
|
+
row_stk.push( node )
|
275
|
+
loop do
|
276
|
+
remove_vertical node
|
277
|
+
node = node.right
|
278
|
+
break if node == col_node
|
279
|
+
end
|
280
|
+
col_node = col_node.down
|
281
|
+
end
|
282
|
+
row_stk
|
283
|
+
end
|
284
|
+
|
285
|
+
=begin rdoc
|
286
|
+
An internal utility method which modifies the "sparse matrix"
|
287
|
+
=end
|
288
|
+
def restore_rows_for_header row_stk
|
289
|
+
until row_stk.empty?
|
290
|
+
node = col_node = row_stk.pop
|
291
|
+
loop do
|
292
|
+
restore_vertical node
|
293
|
+
node = node.right
|
294
|
+
break if node == col_node
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
=begin rdoc
|
300
|
+
An internal utility method which detrmines the column with which to proceed.
|
301
|
+
=end
|
302
|
+
def min_column root
|
303
|
+
node = root.right
|
304
|
+
min = nil
|
305
|
+
while node != root
|
306
|
+
min ||= node
|
307
|
+
if node.count < min.count
|
308
|
+
min = node
|
309
|
+
end
|
310
|
+
node = node.right
|
311
|
+
end
|
312
|
+
min
|
313
|
+
end
|
314
|
+
|
315
|
+
=begin rdoc
|
316
|
+
An internal utility method which modifies a node within the "sparse matrix"
|
317
|
+
=end
|
318
|
+
def restore_horizontal node
|
319
|
+
node.left.right = node
|
320
|
+
node.right.left = node
|
321
|
+
node.header.count = node.header.count + 1
|
322
|
+
end
|
323
|
+
|
324
|
+
=begin rdoc
|
325
|
+
An internal utility method which modifies a node within the "sparse matrix"
|
326
|
+
=end
|
327
|
+
def restore_vertical node
|
328
|
+
node.up.down = node
|
329
|
+
node.down.up = node
|
330
|
+
node.header.count = node.header.count + 1
|
331
|
+
end
|
332
|
+
|
333
|
+
=begin rdoc
|
334
|
+
An internal utility method which modifies a node within the "sparse matrix"
|
335
|
+
=end
|
336
|
+
def remove_horizontal node
|
337
|
+
node.left.right = node.right
|
338
|
+
node.right.left = node.left
|
339
|
+
node.header.count = node.header.count - 1
|
340
|
+
node
|
341
|
+
end
|
342
|
+
|
343
|
+
=begin rdoc
|
344
|
+
An internal utility method which modifies a node within the "sparse matrix"
|
345
|
+
=end
|
346
|
+
def remove_vertical node
|
347
|
+
node.up.down = node.down
|
348
|
+
node.down.up = node.up
|
349
|
+
node.header.count = node.header.count - 1
|
350
|
+
node
|
351
|
+
end
|
352
|
+
|
353
|
+
public
|
354
|
+
|
355
|
+
=begin rdoc
|
356
|
+
Attempts to solve an "exact cover" problem represented by the given arrays.
|
357
|
+
|
358
|
+
* available - an array of boolean arrays. Each boolean array represent one of the available vectors. true <-> 1, false <-> 0. Available vectors will be added to the existing vectors to form the resulting solution.
|
359
|
+
* existing - an array of boolean arrays. Each boolean array represent one of the vectors. true <-> 1, false <-> 0. Existing vectors are assumed to be non-conflicting, and will used as part of the resulting solution.
|
360
|
+
* block - a "block" or Proc. This block will be called for each solution found.
|
361
|
+
|
362
|
+
The return value:
|
363
|
+
* If no block is given then the first solution found will be returned.
|
364
|
+
* If a block is given, and when called returns any value other than false or nil, the algorithm will stop, and return the result from the block.
|
365
|
+
* If a block is given, but the block never returns any value other than false or nil, the total number of solutions found will be returned.
|
366
|
+
|
367
|
+
=end
|
368
|
+
def solve_exact_cover(available, existing = [], &block)
|
369
|
+
matrix = SparseMatrix.new available
|
370
|
+
existing.each do |row|
|
371
|
+
row.each_index do |i|
|
372
|
+
if row[i]
|
373
|
+
node = matrix.root.right
|
374
|
+
unless until node == matrix.root
|
375
|
+
if node.index == i
|
376
|
+
remove_rows_for_header node
|
377
|
+
remove_horizontal node
|
378
|
+
break true
|
379
|
+
end
|
380
|
+
node = node.right
|
381
|
+
end
|
382
|
+
then
|
383
|
+
throw "Column conflict or not found."
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
solution = existing.clone
|
390
|
+
solve(matrix.root, [], 0) do |sel|
|
391
|
+
sel.each do |row|
|
392
|
+
solution.push(available[row])
|
393
|
+
end
|
394
|
+
|
395
|
+
unless block == nil
|
396
|
+
if(result = block.call(solution))
|
397
|
+
return result
|
398
|
+
end
|
399
|
+
else
|
400
|
+
return solution
|
401
|
+
end
|
402
|
+
solution = existing.clone
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
=begin rdoc
|
407
|
+
This utility method will convert an array of 0's and 1's to an array of booleans.
|
408
|
+
|
409
|
+
Used to facilitate the displaying of 0's and 1's in problems/solution, which is easier to read.
|
410
|
+
=end
|
411
|
+
def convert_row_fixnum_to_boolean ary
|
412
|
+
Array.new(ary.length) { |i| ary[i] == 1 } unless ary == nil
|
413
|
+
end
|
414
|
+
|
415
|
+
=begin rdoc
|
416
|
+
This utility method will convert an array of fixnum arrays (0's and 1's) to an array of boolean arrays.
|
417
|
+
|
418
|
+
Used to facilitate the displaying of 0's and 1's in problems/solution, which is easier to read.
|
419
|
+
=end
|
420
|
+
def convert_rows_fixnum_to_boolean ary
|
421
|
+
Array.new(ary.length) { |i| convert_row_fixnum_to_boolean ary[i] } unless ary == nil
|
422
|
+
end
|
423
|
+
|
424
|
+
=begin rdoc
|
425
|
+
This utility method will convert an array of booleans to an array of 0's and 1's
|
426
|
+
|
427
|
+
Used to facilitate the displaying of 0's and 1's in problems/solution, which is easier to read.
|
428
|
+
=end
|
429
|
+
def convert_row_boolean_to_fixnum ary
|
430
|
+
Array.new(ary.length) { |i| ary[i] ? 1 : 0 } unless ary == nil
|
431
|
+
end
|
432
|
+
|
433
|
+
=begin rdoc
|
434
|
+
This utility method will convert an array of boolean arrays to an array of fixnum arrays (0's and 1's).
|
435
|
+
|
436
|
+
Used to facilitate the displaying of 0's and 1's in problems/solution, which is easier to read.
|
437
|
+
=end
|
438
|
+
def convert_rows_boolean_to_fixnum ary
|
439
|
+
Array.new(ary.length) { |i| convert_row_boolean_to_fixnum ary[i] } unless ary == nil
|
440
|
+
end
|
441
|
+
|
442
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
|
2
|
+
# Copyright (c) 2006 Justin W Smith
|
3
|
+
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
5
|
+
# software and associated documentation files (the "Software"), to deal in the Software
|
6
|
+
# without restriction, including without limitation the rights to use, copy, modify,
|
7
|
+
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to the following
|
9
|
+
# conditions:
|
10
|
+
|
11
|
+
# The above copyright notice and this permission notice shall be included in all copies
|
12
|
+
# or substantial portions of the Software.
|
13
|
+
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
15
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
16
|
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
17
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
18
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
19
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
|
21
|
+
|
22
|
+
require 'test/unit'
|
23
|
+
require 'lib/dancing_links'
|
24
|
+
|
25
|
+
class Array
|
26
|
+
def to_s
|
27
|
+
s = "["
|
28
|
+
each_index do |i|
|
29
|
+
s << " " + self[i].to_s
|
30
|
+
s << ", " unless (i == self.length-1)
|
31
|
+
end
|
32
|
+
s << "]"
|
33
|
+
end
|
34
|
+
|
35
|
+
def comb_helper head, tail, &block
|
36
|
+
if tail.length == 0
|
37
|
+
block.call(head)
|
38
|
+
else
|
39
|
+
tail.each_index do |i|
|
40
|
+
t = tail.clone
|
41
|
+
t.delete_at(i)
|
42
|
+
comb_helper head.clone << tail[i], t, &block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def combinations &block
|
48
|
+
comb_helper [], self, &block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class TC_DancingLinks < Test::Unit::TestCase
|
53
|
+
include DancingLinks
|
54
|
+
|
55
|
+
def setup
|
56
|
+
@a1 = [ [1, 0, 1], [0, 0, 1], [1, 1, 0], [ 1, 0, 0] ]
|
57
|
+
@b1 = [ [ true, false, true],
|
58
|
+
[false, false, true],
|
59
|
+
[true, true, false],
|
60
|
+
[true, false, false] ]
|
61
|
+
@matrix1 = SparseMatrix.new convert_rows_fixnum_to_boolean(@a1)
|
62
|
+
@s1 = [ [ 0, 0, 1], [ 1, 1, 0] ]
|
63
|
+
|
64
|
+
@a2 = [ [1, 0, 1], [1, 1, 0], [0, 1, 1], [ 0, 0, 0] ]
|
65
|
+
@e2 = [ [0, 0, 1] ]
|
66
|
+
@matrix2 = SparseMatrix.new convert_rows_fixnum_to_boolean(@a2)
|
67
|
+
@s2 = [ [0, 0, 1], [ 1, 1, 0] ]
|
68
|
+
|
69
|
+
@a3 = [ [1, 0, 1], [1, 1, 0], [0, 1, 1], [ 1, 1, 1] ]
|
70
|
+
@matrix3 = SparseMatrix.new convert_rows_fixnum_to_boolean(@a3)
|
71
|
+
@s3 = [[1, 1, 1]]
|
72
|
+
|
73
|
+
@a4 = [ [1, 0, 1], [1, 1, 0], [0, 1, 1], [ 0, 1, 0] ]
|
74
|
+
@matrix4 = SparseMatrix.new convert_rows_fixnum_to_boolean(@a4)
|
75
|
+
@s4 = [[1, 0, 1], [0, 1, 0]]
|
76
|
+
|
77
|
+
@a5 = [ [1, 0, 1], [1, 1, 0], [1, 1, 1], [ 0, 1, 1] ]
|
78
|
+
@matrix5 = SparseMatrix.new convert_rows_fixnum_to_boolean(@a4)
|
79
|
+
@s5 = [[1, 1, 1]]
|
80
|
+
|
81
|
+
@a6 = [ [1, 0, 1], [1, 0, 0], [1, 1, 1], [ 0, 1, 1], [1, 1, 0] ]
|
82
|
+
|
83
|
+
@a7 = [ [0, 0, 1], [ 0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1] ]
|
84
|
+
@s7 = [
|
85
|
+
[ [ 1, 1, 1] ],
|
86
|
+
[ [ 0, 1, 1], [ 1, 0, 0 ] ].sort,
|
87
|
+
[ [ 1, 0, 1], [ 0, 1, 0 ] ].sort,
|
88
|
+
[ [ 1, 1, 0], [ 0, 0, 1] ].sort,
|
89
|
+
[ [ 0, 0, 1], [0, 1, 0], [1, 0, 0] ].sort ]
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_convert_rows
|
93
|
+
assert_equal(@a1, convert_rows_boolean_to_fixnum(@b1) )
|
94
|
+
assert_equal(@b1, convert_rows_fixnum_to_boolean(@a1) )
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_sparse_matrix
|
98
|
+
root = @matrix1.root
|
99
|
+
assert_equal(3, root.count)
|
100
|
+
assert_equal(3, root.right.count)
|
101
|
+
assert_equal(1, root.right.right.count)
|
102
|
+
assert_equal(2, root.right.right.right.count)
|
103
|
+
assert_equal(root.right.up.left, root.right.up)
|
104
|
+
assert_equal(root.left.up.left, root.right.right.right.down.down)
|
105
|
+
assert_equal(root.right.down.header, root.right)
|
106
|
+
assert_equal(root.left.up.header, root.left)
|
107
|
+
assert_equal(root.right.right.right.right, root)
|
108
|
+
assert_equal(root.left.left.left.left, root)
|
109
|
+
|
110
|
+
assert_equal(0, root.left.down.row_num)
|
111
|
+
assert_equal(1, root.left.down.down.row_num)
|
112
|
+
assert_equal(2, root.right.right.down.row_num)
|
113
|
+
assert_equal(3, root.right.up.row_num)
|
114
|
+
|
115
|
+
assert_equal(0, root.right.index)
|
116
|
+
assert_equal(3, root.right.count)
|
117
|
+
assert_equal(1, root.right.right.index)
|
118
|
+
assert_equal(1, root.right.right.count)
|
119
|
+
assert_equal(2, root.right.right.right.index)
|
120
|
+
assert_equal(2, root.right.right.right.count)
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_remove_rows_for_header
|
125
|
+
root = @matrix1.root
|
126
|
+
remove_rows_for_header root.right
|
127
|
+
assert_equal( 0, root.right.count)
|
128
|
+
assert_equal( 0, root.right.right.count)
|
129
|
+
assert_equal( 1, root.right.right.right.count)
|
130
|
+
assert_equal( root.right.right.right, root.left)
|
131
|
+
assert_equal( root.left.down, root.right.right.right.up)
|
132
|
+
assert_equal( 1, root.left.down.row_num)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_restore_rows_for_header
|
136
|
+
root = @matrix1.root
|
137
|
+
restore_rows_for_header( remove_rows_for_header( root.right))
|
138
|
+
test_sparse_matrix
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_select_row
|
142
|
+
root = @matrix1.root
|
143
|
+
header_stk, row_stk_stk = select_row root.right.right.down
|
144
|
+
|
145
|
+
assert_equal( 1, root.count)
|
146
|
+
assert_equal( 1, root.right.count)
|
147
|
+
assert_equal( 2, root.right.index )
|
148
|
+
assert_equal( root.right.down, root.left.down)
|
149
|
+
assert_equal( root.right.down.down, root.left)
|
150
|
+
assert_equal( root.right.down.down, root.left)
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_restore_selected_row
|
155
|
+
|
156
|
+
root = @matrix1.root
|
157
|
+
restore_selected_row( *select_row( root.right.right.down))
|
158
|
+
test_sparse_matrix
|
159
|
+
restore_selected_row( *select_row( root.right.down))
|
160
|
+
test_sparse_matrix
|
161
|
+
restore_selected_row( *select_row( root.left.down))
|
162
|
+
test_sparse_matrix
|
163
|
+
restore_selected_row( *select_row( root.left.up))
|
164
|
+
test_sparse_matrix
|
165
|
+
restore_selected_row( *select_row( root.right.up))
|
166
|
+
test_sparse_matrix
|
167
|
+
restore_selected_row( *select_row( root.left.up))
|
168
|
+
test_sparse_matrix
|
169
|
+
|
170
|
+
header_stk, row_stk_stk = select_row( root.right.right.down)
|
171
|
+
restore_selected_row( *select_row( root.right.down) )
|
172
|
+
restore_selected_row( header_stk, row_stk_stk)
|
173
|
+
test_sparse_matrix
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_min_column
|
178
|
+
root = @matrix1.root
|
179
|
+
header = min_column root
|
180
|
+
assert_equal( root.right.right, header)
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_solve_exact_cover
|
184
|
+
s = convert_rows_boolean_to_fixnum(solve_exact_cover( @b1))
|
185
|
+
assert_equal(@s1.sort, s.sort)
|
186
|
+
|
187
|
+
s = solve_exact_cover( convert_rows_fixnum_to_boolean(@a2))
|
188
|
+
assert_equal(0, s)
|
189
|
+
|
190
|
+
s = convert_rows_boolean_to_fixnum(solve_exact_cover( convert_rows_fixnum_to_boolean(@a2), convert_rows_fixnum_to_boolean(@e2)))
|
191
|
+
assert_equal(@s2.sort, s.sort)
|
192
|
+
|
193
|
+
s = convert_rows_boolean_to_fixnum(solve_exact_cover( convert_rows_fixnum_to_boolean(@a3)))
|
194
|
+
assert_equal(@s3.sort, s.sort)
|
195
|
+
|
196
|
+
s = convert_rows_boolean_to_fixnum(solve_exact_cover( convert_rows_fixnum_to_boolean(@a4)))
|
197
|
+
assert_equal(@s4.sort, s.sort)
|
198
|
+
|
199
|
+
s = convert_rows_boolean_to_fixnum(solve_exact_cover( convert_rows_fixnum_to_boolean(@a5)))
|
200
|
+
assert_equal(@s5.sort, s.sort)
|
201
|
+
|
202
|
+
s = solve_exact_cover( convert_rows_fixnum_to_boolean(@a6)){false}
|
203
|
+
assert_equal(2, s)
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_solve_exact_cover2
|
208
|
+
|
209
|
+
@a7.combinations do |comb|
|
210
|
+
s = @s7.clone
|
211
|
+
solve_exact_cover(convert_rows_fixnum_to_boolean(comb)) do |result|
|
212
|
+
result = convert_rows_boolean_to_fixnum( result ).sort!
|
213
|
+
assert( s.member?( result ), "\ncomb: #{comb.to_s}\nresult: #{result}\nsolutions left: #{s.to_s}")
|
214
|
+
s.delete result
|
215
|
+
false
|
216
|
+
end
|
217
|
+
assert_equal( [], s)
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
metadata
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: dancing-links
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2006-01-28
|
8
|
+
summary: Implementation of the "Dancing Links" algorithm.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: justin dot w dot smith at gmail dot com
|
12
|
+
homepage: http://rubyforge.org/projects/sudoku-gtk/
|
13
|
+
rubyforge_project:
|
14
|
+
description: Dancing-links is an implementation of the "Dancing Links" algorthm to solve the "Exact Cover" problem. Algorithm found by Donald Knuth.
|
15
|
+
autorequire: dancing_links
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors: []
|
28
|
+
files:
|
29
|
+
- lib/dancing_links.rb
|
30
|
+
- lib/array_combs_perms.rb
|
31
|
+
test_files:
|
32
|
+
- test/tc_dancing_links.rb
|
33
|
+
rdoc_options: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
requirements:
|
38
|
+
- none
|
39
|
+
dependencies: []
|