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.
@@ -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: []