dancing-links 0.1

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