doku 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,32 @@
1
+ source "http://rubygems.org"
2
+
3
+ # This gem uses new features of ruby:
4
+ # require_relative
5
+ # Enumerator
6
+ #
7
+ # To get access to these features in older versions, we use the
8
+ # backports gem:
9
+ gem "backports", :platforms => :ruby_18
10
+
11
+ # For development gem, these gems are recommended:
12
+ group :development do
13
+ gem "rspec"
14
+ gem "bundler"
15
+ gem "jeweler", "~> 1.6.2"
16
+ gem "rcov", ">= 0", :platforms => :mri_18
17
+ gem "yard"
18
+ gem "watchr"
19
+ gem "ruby-prof", :platforms => :mri
20
+
21
+ platform :mri_19 do
22
+ # We need the latest version of linecache19 for debugging to work.
23
+ # http://stackoverflow.com/questions/8251349/ruby-threadptr-data-type-error
24
+ gem 'linecache19', :git => 'git://github.com/mark-moseley/linecache'
25
+
26
+ # ruby-debug-base19 0.11.26 is not available on ruby gems, needs to be manually
27
+ # preinstalled. See ruby1.9.3_dev_setup.sh
28
+ gem 'ruby-debug-base19', '>= 0.11.26'
29
+
30
+ gem 'ruby-debug19'
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ = Doku: solve Sudoku-like puzzles!
2
+
3
+ Doku is a Ruby[http://www.rubylang.org/] gem for solving Sudoku-like puzzles using the {Dancing Links}[http://en.wikipedia.org/wiki/Dancing_Links] algorithm by Donald Knuth.
4
+
5
+ == Installation
6
+
7
+ At the command line, type
8
+
9
+ gem install doku
10
+
11
+ You might need to prefix that with <code>sudo</code> depending on where your gems are stored.
12
+
13
+ == Example code
14
+
15
+ :include: example.rb
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'rake'
4
+ require 'jeweler'
5
+ require 'rspec/core'
6
+ require 'rspec/core/rake_task'
7
+ require 'yard'
8
+ rescue LoadError => e
9
+ $stderr.puts "Run `gem install bundler && bundle install` to install missing gems."
10
+ exit 1
11
+ end
12
+
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "doku"
16
+ gem.homepage = "http://github.com/DavidEGrayson/doku"
17
+ gem.license = "MIT"
18
+ gem.files = %w{.document *.txt *.rdoc VERSION
19
+ Gemfile Rakefile
20
+ lib/doku.rb
21
+ lib/doku/*.rb
22
+ spec/*.rb
23
+ }
24
+
25
+ gem.summary = "Ruby library for solving sudoku, hexadoku, and similar puzzles."
26
+ gem.description = <<END
27
+ This gem allows you to represent Sudoku-like puzzles
28
+ (Sudoku, Hexadoku, and Hexamurai) as objects and find
29
+ solutions for them.
30
+
31
+ This gem contains a reusable, pure ruby implementation of the
32
+ Dancing Links algorithm by Donald Knuth.
33
+ END
34
+ gem.email = "davidegrayson@gmail.com"
35
+ gem.authors = ["David Grayson"]
36
+ # dependencies defined in Gemfile
37
+ end
38
+ Jeweler::RubygemsDotOrgTasks.new
39
+
40
+ RSpec::Core::RakeTask.new(:spec) do |spec|
41
+ spec.pattern = FileList['spec/**/*_spec.rb']
42
+ end
43
+
44
+ YARD::Rake::YardocTask.new do |t|
45
+ t.files = %w{lib/**/*.rb}
46
+ t.options = %w{}
47
+ end
48
+
49
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,4 @@
1
+ require 'backports' unless defined? require_relative
2
+ require_relative 'doku/sudoku.rb'
3
+ require_relative 'doku/hexadoku.rb'
4
+ require_relative 'doku/hexamurai.rb'
@@ -0,0 +1,561 @@
1
+ require 'backports' unless defined?(Enumerator)
2
+ require 'set'
3
+
4
+ module Doku; end
5
+
6
+ # This module contains a general-purpose implementation of the
7
+ # {http://arxiv.org/abs/cs/0011047 Dancing Links}
8
+ # algorithm discovered by Donald Knuth for solving
9
+ # {http://en.wikipedia.org/wiki/Exact_cover exact cover problems}.
10
+ # This module is included in the Doku gem for convenience, but it really has
11
+ # nothing to do with solving Sudoku-like puzzles; it can be applied to any
12
+ # exact cover problem.
13
+ # The main class in this module is {LinkMatrix}. All the other classes and
14
+ # modules are helpers for this class.
15
+ module Doku::DancingLinks
16
+ # The data structures used here are too complicated
17
+ # and interconnected for Ruby to efficiently inspect them.
18
+ # Without this module, even a 7x6 {LinkMatrix} takes
19
+ # many many seconds to inspect.
20
+ module Uninspectable
21
+ # Simply calls to_s.
22
+ # @return (String)
23
+ def inspect
24
+ to_s
25
+ end
26
+ end
27
+
28
+ # This is a class that lets us concisely enumerate a certain
29
+ # set of objects by traveling either up, down, left, or right
30
+ # from a starting object until we wrap around and reach that same node.
31
+ # Since this class includes the Enumerable class, it has several fancy
32
+ # methods available with it such as "max_by", "collect", or "to_a".
33
+ class LinkEnumerator
34
+ include Enumerable
35
+
36
+ # @param link (Symbol) The link to follow. Should be :up, :down, :left, or :right.
37
+ # @param start (Object) The starting object. Typically a {LinkMatrix::Node}, {LinkMatrix::Column} (column header), or {LinkMatrix} (root node).
38
+ # @param include_start (Boolean) True if we want to include the starting object in this enumeration.
39
+ def initialize(link, start, include_start=false)
40
+ @link, @start, @include_start = link, start, include_start
41
+ end
42
+
43
+ # Iterates through objects by starting at the starting object
44
+ # and going in the specified direction until the start point is
45
+ # found again.
46
+ # The starting object will be yielded first, if this LinkEnumerator
47
+ # was configured to yield it {#initialize}.
48
+ # @yield (obj)
49
+ def each
50
+ yield @start if @include_start
51
+
52
+ n = @start
53
+ while true
54
+ n = n.send @link
55
+ return if n == @start
56
+ yield n
57
+ end
58
+ end
59
+ end
60
+
61
+ # This module is mixed into objects to give them their "left"
62
+ # and "right" links, and to give some convenient methods for
63
+ # changing those links.
64
+ # Every class this module is added to gains to attr_accessors:
65
+ # left and right.
66
+ module HorizontalLinks
67
+ include Uninspectable
68
+
69
+ def self.included(klass)
70
+ klass.instance_eval do
71
+ attr_accessor :left, :right
72
+ end
73
+ end
74
+
75
+ # Removes this object from the horizontal linked list by making the
76
+ # left and right neighbors point at each other instead of this object.
77
+ # This can later be undone with {#reinsert_horizontal}
78
+ def remove_horizontal
79
+ right.left, left.right = left, right
80
+ end
81
+
82
+ # Reinserts this object into the horizontal linked list by making the
83
+ # former left and right neighors point to this object instead of each other.
84
+ # The former left and right neighbors are simply found by looking at the
85
+ # "left" and "right" links for this object, which still point to them.
86
+ # This undoes the effect of {#remove_horizontal}.
87
+ def reinsert_horizontal
88
+ left.right = right.left = self
89
+ end
90
+
91
+ # Inserts this object to the left of the specified object in the
92
+ # horizontal list.
93
+ def insert_left(obj)
94
+ self.left, self.right = obj.left, obj
95
+ reinsert_horizontal
96
+ end
97
+ end
98
+
99
+ # This module is mixed into objects to give them their "up"
100
+ # and "down" links, and to give some convenient methods for
101
+ # changing those links.
102
+ # Every class this module is added to gains to attr_accessors:
103
+ # up and down.
104
+ module VerticalLinks
105
+ include Uninspectable
106
+
107
+ def self.included(klass)
108
+ klass.instance_eval do
109
+ attr_accessor :up, :down
110
+ end
111
+ end
112
+
113
+ # Removes this object from the vertical linked list by making the
114
+ # up and down neighbors point at each other instead of this object.
115
+ # This can later be undone with {#reinsert_vertical}
116
+ def remove_vertical
117
+ down.up, up.down = up, down
118
+ end
119
+
120
+ # Reinserts this object into the vertical linked list by making the
121
+ # former up and down neighors point to this object instead of each other.
122
+ # The former up and down neighbors are simply found by looking at the
123
+ # "up" and "down" links for this object, which still point to them.
124
+ # This undoes the effect of {#remove_vertical}.
125
+ def reinsert_vertical
126
+ up.down = down.up = self
127
+ end
128
+
129
+ # Inserts this object above the specified object in the
130
+ # vertical list.
131
+ def insert_above(other)
132
+ self.up, self.down = other.up, other
133
+ reinsert_vertical
134
+ end
135
+ end
136
+
137
+ # A LinkMatrix object is the Root object from Donald Knuth's paper on
138
+ # Dancing Links. It also represents the matrix as a whole, so it has
139
+ # methods for building the matrix and finding exact covers.
140
+ # This data structure is used to efficiently implement Algorithm X,
141
+ # allowing us to find exact covers.
142
+ class LinkMatrix
143
+
144
+ # The Column Header object from Knuth.
145
+ # This object represents a requirement that needs to be satisfied
146
+ # at least once in the exact coer problem.
147
+ class Column
148
+ include HorizontalLinks, VerticalLinks
149
+
150
+ # An ID object provided by the user to give meaning to the column.
151
+ # This is the N relation from Knuth.
152
+ # The ID can be any object.
153
+ attr_reader :id
154
+
155
+ # The current number of nodes in this column.
156
+ # If this is zero, it means the column can not be covered, given
157
+ # choices that have already been made.
158
+ attr_accessor :size
159
+
160
+ # Initializes an empty column with the specified id.
161
+ # The ID can be any object.
162
+ def initialize(id)
163
+ @up = @down = self
164
+ @id = id
165
+ @size = 0
166
+ end
167
+
168
+ # All the nodes in this column, starting at the top one and going down.
169
+ def nodes_downward
170
+ LinkEnumerator.new :down, self
171
+ end
172
+
173
+ # All the nodes in this column, starting at the bottom one and going up.
174
+ def nodes_upward
175
+ LinkEnumerator.new :up, self
176
+ end
177
+
178
+ alias :nodes :nodes_downward
179
+
180
+ # Covers the column.
181
+ # This algorithm comes from page 6 of Knuth's paper.
182
+ # This operation removes the column from the list of
183
+ # columns that needs to be covered and it removes all
184
+ # rows that would cover this column.
185
+ # This can be efficiently undone with {#uncover}.
186
+ #
187
+ # The word "cover" here means the same thing it does in
188
+ # the phrase "exact cover problem". Our goal is to
189
+ # cover every column exactly once using this method.
190
+ def cover
191
+ remove_horizontal
192
+ nodes_downward.each do |i|
193
+ i.nodes_except_self_rightward.each do |j|
194
+ j.remove_vertical
195
+ j.column.size -= 1
196
+ end
197
+ end
198
+ end
199
+
200
+ # Uncovers the column.
201
+ # This algorithm comes from page 6 of Knuth's paper.
202
+ # This operation undoes the effects of {#cover}.
203
+ def uncover
204
+ nodes_upward.each do |i|
205
+ i.nodes_except_self_leftward.each do |j|
206
+ j.column.size += 1
207
+ j.reinsert_vertical
208
+ end
209
+ end
210
+ reinsert_horizontal
211
+ end
212
+
213
+ # True if there are no more nodes in this column.
214
+ # @return (Boolean)
215
+ def empty?
216
+ size == 0 # Equivalent to (down == self)
217
+ end
218
+ end
219
+
220
+ # This class represents a normal node in Knuth's {LinkMatrix}.
221
+ # Every node belongs to a column and a row, and it represents the
222
+ # fact that the row (i.e. set) "covers" the column.
223
+ class Node
224
+ include HorizontalLinks, VerticalLinks
225
+
226
+ # The {Column} object that this node belongs to.
227
+ attr_accessor :column
228
+
229
+ # The user-assigned ID of the row this node belongs to.
230
+ attr_accessor :row_id
231
+
232
+ # All nodes in the same row, starting with self and going to the right.
233
+ def nodes_rightward
234
+ LinkEnumerator.new :right, self, true
235
+ end
236
+
237
+ # All nodes in the same row, starting with self and going to the right,
238
+ # but not including self.
239
+ def nodes_except_self_rightward
240
+ LinkEnumerator.new :right, self
241
+ end
242
+
243
+ # All nodes in the same row, starting with self and going to the left,
244
+ # but not including self.
245
+ def nodes_except_self_leftward
246
+ LinkEnumerator.new :left, self
247
+ end
248
+
249
+ alias :nodes_except_self :nodes_except_self_rightward
250
+
251
+ alias :nodes :nodes_rightward
252
+
253
+ # Removes a row from the {LinkMatrix} by covering every
254
+ # column that it touches. This represents (tentatively)
255
+ # choosing the node's row to be in our exact cover.
256
+ # When that choice is proven to not work, this action can
257
+ # be efficiently undone with {#unchoose}.
258
+ def choose
259
+ nodes_except_self_rightward.each do |node|
260
+ node.column.cover
261
+ end
262
+ end
263
+
264
+ # Undoes the effect of {#choose}, putting
265
+ # the nodes of the row back into the {LinkMatrix}.
266
+ def unchoose
267
+ nodes_except_self_leftward.each do |node|
268
+ node.column.uncover
269
+ end
270
+ end
271
+
272
+ end
273
+
274
+ # Since the LinkMatrix object is the root node, it has left and right links.
275
+ include HorizontalLinks
276
+
277
+ # Creates a new, empty matrix with no columns and no rows.
278
+ def initialize
279
+ @left = @right = self
280
+ @columns = {} # column_id object => Column
281
+ @rows = {} # row_id object => Node
282
+ end
283
+
284
+ # Enumerable for all the {Column}s in the matrix.
285
+ def columns
286
+ LinkEnumerator.new :right, self
287
+ end
288
+
289
+ # True if there are no more columns left in the matrix (they were all covered).
290
+ # @return (Boolean)
291
+ def empty?
292
+ right == self
293
+ end
294
+
295
+ # Creates a new column with the specified ID and inserts it
296
+ # into the matrix as the right-most column.
297
+ # @param id (Object) Any object that uniquely identifies the column.
298
+ # @return (Column) Newly created column.
299
+ def create_column(id)
300
+ column = Column.new(id)
301
+ column.insert_left self
302
+ return @columns[id] = column
303
+ end
304
+
305
+ # Retrieves a column object by its ID or returns nil if there is
306
+ # no column with that ID.
307
+ # @return (Column)
308
+ def column(id)
309
+ @columns[id]
310
+ end
311
+
312
+ # Retrieves a column object by its ID or creates a new one if
313
+ # it didn't exist already.
314
+ # @return (Column)
315
+ def find_or_create_column(id)
316
+ @columns[id] || create_column(id)
317
+ end
318
+
319
+ # Creates a new {LinkMatrix} to represent an
320
+ # {http://en.wikipedia.org/wiki/Exact_cover exact cover problem}.
321
+ #
322
+ # Every set in the exact cover problem will be represented by a row in the
323
+ # matrix.
324
+ #
325
+ # Every element in the universe of the exact cover problem will be represented
326
+ # by a column in the matrix. The universe is inferred by taking the union
327
+ # of all the sets in the sets parameter, but if you want to have control over
328
+ # the order of the columns then you can also make a universe array and
329
+ # pass it in to the universe parameter.
330
+ #
331
+ # In {LinkMatrix}, every row has a row id. The row id is used to express
332
+ # exact covers when they are found.
333
+ # You can just let all the row ids be equal to the sets themselves by
334
+ # making the sets parameter be an Array or Set of sets, or
335
+ # you can specify the row ids explicitly by if you make the sets parameter
336
+ # be a hash that associates row ids to sets.
337
+ #
338
+ # @param (Object) sets Either a hash associating row_ids to sets, or just
339
+ # an array of sets. A set is an Array or Set of objects in the
340
+ # universe of the exact cover problem.
341
+ # @param universe (Array) This parameter is optional. If provided, it
342
+ # will define the order of the first columns of the link matrix.
343
+ # It is OK if there are elements in the sets that are not present in
344
+ # this array.
345
+ # @return (LinkMatrix)
346
+ def self.from_sets(sets, universe=[])
347
+ matrix = new
348
+ universe.each do |column_id|
349
+ matrix.find_or_create_column column_id
350
+ end
351
+
352
+ if sets.is_a? Hash
353
+ sets.each do |row_id, column_ids|
354
+ matrix.add_row column_ids, row_id
355
+ end
356
+ else
357
+ sets.each do |column_ids|
358
+ matrix.add_row column_ids
359
+ end
360
+ end
361
+
362
+ matrix
363
+ end
364
+
365
+ # Adds a row to the matrix.
366
+ # If a column_id is not recognized, it will be added to the matrix
367
+ # as a new column.
368
+ #
369
+ # @param column_ids (Enumerable) The column_ids that are in this row.
370
+ # @param row_id (Object) The id of this row. This is used to express express exact covers and as the argument to {#remove_row}.
371
+ def add_row(column_ids, row_id=column_ids.dup)
372
+ first_node = nil
373
+ Set.new(column_ids).each do |column_id|
374
+ column = find_or_create_column(column_id)
375
+ node = Node.new
376
+
377
+ # Set the vertical links and column.
378
+ node.column = column
379
+ node.insert_above column
380
+
381
+ # Set the horizontal links and row_id.
382
+ node.row_id = row_id
383
+ if first_node.nil?
384
+ @rows[row_id] = first_node = node.left = node.right = node
385
+ else
386
+ node.insert_left first_node
387
+ end
388
+
389
+ column.size += 1
390
+ end
391
+ end
392
+
393
+ # Removes a row from the matrix.
394
+ # @param row_id (Object) The ID of the row that was specified when
395
+ # {#add_row} was called.
396
+ def remove_row(row_id)
397
+ raise ArgumentError, "Row with id #{row_id} not found." if !@rows[row_id]
398
+ @rows[row_id].nodes_rightward.each do |node|
399
+ node.column.cover
400
+ end
401
+ end
402
+
403
+ # Retrieves a node in the row with the specified ID or returns nil if there is
404
+ # no row with that ID.
405
+ # @param id (Object) The ID of the row that was specified when
406
+ # {#add_row} was called.
407
+ # @return (Node)
408
+ def row(id)
409
+ @rows[id]
410
+ end
411
+
412
+ # This is a recursive method that finds the first exact cover of a
413
+ # LinkMatrix that represents an exact cover problem, using the
414
+ # the algorithm described on page 5 of Donald Knuth's paper "Dancing Links".
415
+ # This method is just here for purists who want to be sure they are using
416
+ # Donald Knuth's algorithm.
417
+ # For most uses, it is recommended to use the more flexible, non-recursive
418
+ # function {#each_exact_cover} and the methods based on it: {#exact_covers}
419
+ # and {#find_exact_cover}.
420
+ # @return (Array) Array of row_ids of the rows/sets that are in the cover,
421
+ # or nil if no exact cover was found.
422
+ def find_exact_cover_recursive(k=0, o=[])
423
+ if right == self
424
+ return o[0...k].collect &:row_id # Success
425
+ end
426
+
427
+ c = smallest_column
428
+ c.cover
429
+
430
+ c.nodes_downward.each do |r|
431
+ o[k] = r
432
+
433
+ r.nodes_except_self_rightward.each do |j|
434
+ j.column.cover
435
+ end
436
+
437
+ if answer = find_exact_cover_recursive(k+1, o)
438
+ return answer # Success
439
+ end
440
+
441
+ r.nodes_except_self_leftward.each do |j|
442
+ j.column.uncover
443
+ end
444
+ end
445
+
446
+ c.uncover
447
+ return nil
448
+ end
449
+
450
+ # TODO: see if recursive or non-recursive algorithm is faster.
451
+
452
+ # Searches for an exact cover.
453
+ # NOTE: This method mutates the LinkMatrix.
454
+ # @return (Array) Array of row ids of the rows/sets that are in the cover,
455
+ # or nil if no exact cover was found.
456
+ def find_exact_cover
457
+ exact_covers.first
458
+ end
459
+
460
+ # Returns an enumerable that searches for exact covers as its elements
461
+ # are enumerated.
462
+ # NOTE: This method mutates the LinkMatrix.
463
+ # @return (Enumerable) Enumerable of exact covers. Each exact cover is
464
+ # an array of row ids of the rows/sets that are in the cover.
465
+ def exact_covers
466
+ Enumerator.new do |y|
467
+ each_exact_cover do |ec|
468
+ y << ec
469
+ end
470
+ end
471
+ end
472
+
473
+ # Searches for exact covers and yields them as it finds them.
474
+ # NOTE: This method mutates the LinkMatrix while it is running, but
475
+ # when it is finished the matrix will be back to its original state.
476
+ # @yield exact_cover
477
+ # @yieldparam exact_cover (Array) Array of row_ids of the rows/sets that are
478
+ # in the cover.
479
+ def each_exact_cover
480
+ nodes = [] # List of nodes that are currently "covered"
481
+
482
+ while true
483
+
484
+ if empty?
485
+ # Success. Matrix is empty because every column is covered once.
486
+ yield nodes.collect &:row_id
487
+ end
488
+
489
+ if column = choose_column
490
+ # Cover a new column and pick the first node in it.
491
+ column.cover
492
+ node = column.down
493
+ else
494
+ # Uncover columns until we find one with a node we haven't tried.
495
+ node = backtrack!(nodes)
496
+ return if node.nil? # Tried everything
497
+ end
498
+
499
+ # Try the node (push it and cover its columns).
500
+ nodes.push node
501
+ node.choose
502
+
503
+ end
504
+ end
505
+
506
+ protected
507
+
508
+ def choose_column
509
+ return nil if empty?
510
+ column = smallest_column
511
+ return nil if column.empty?
512
+ return column
513
+ end
514
+
515
+ # When choosing a column, we use Knuth's S heuristic.
516
+ # Assumption: The matrix has at least one column.
517
+ def smallest_column
518
+ # Slow but concise version of this method:
519
+ #return columns.min_by &:size
520
+
521
+ column = smallest = right
522
+ min_size = column.size
523
+ while true
524
+ column = column.right
525
+ return smallest if column == self
526
+
527
+ if column.size < min_size
528
+ smallest, min_size = column, column.size
529
+ return smallest if min_size == 0
530
+ end
531
+ end
532
+ end
533
+
534
+ # This is used by each_exact_cover.
535
+ # Picks the next node to try by back-tracking until we get
536
+ # to a column where we haven't tried all the nodes, uncovering
537
+ # nodes and columns as it goes.
538
+ # Returns nil if we are done searching the entire solution space.
539
+ def backtrack!(nodes)
540
+ while true
541
+ return nil if nodes.empty? # backtracked back to 0, so we are done
542
+
543
+ # We tried nodes.last and it didn't work, so
544
+ # pop it off and uncover the corresponding columns.
545
+ node = nodes.pop
546
+ node.unchoose
547
+
548
+ # Try the next node in this column.
549
+ x = node.down
550
+
551
+ return x unless x.is_a? Column
552
+
553
+ # Our downwards iteration has gone full-circle
554
+ # back to the column object where it started.
555
+ x.uncover # Uncover the column.
556
+ end
557
+ end
558
+
559
+ end
560
+
561
+ end