dancing-links 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|