sudoku_bardi 0.1.0

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,5 @@
1
+ *.gem
2
+ .rvmrc
3
+ Gemfile.lock
4
+ doc/
5
+ experimental/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sudoku_bardi.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Bardi Einarsson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ 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
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ sudoku\_bardi
2
+ =============
3
+
4
+ ## Summary
5
+
6
+ Sudoku solver, validator and generator
7
+
8
+ ## Description
9
+
10
+ sudoku\_bardi (see websudoku.com) is a set of tools for solving, validating and generating Sudoku puzzles.
11
+
12
+ ## Why should one the sudoku\_bardi tools?
13
+
14
+ The Sudoku solution algorithm does not use recursion -- runs in limited memory and can find all solutions of incorrect puzzles.
15
+
16
+ ## Version
17
+
18
+ v0.1.0 with comprehensive tests, was developed using ruby 1.9.3 and rspec 2.13.0. It does not have any specific Sudoku puzzle generators yet; however it provides a good foundation for any generator as it provides validation of solution uniqueness and shows the operations used in the puzzle solution(s).
19
+
20
+ ## Installation
21
+
22
+ Get the v0.1.0 gem or clone the repository.
23
+
24
+ ## Usage and documentation
25
+
26
+ Study the programs in the spec/ directory. See below for more information.
27
+
28
+ Try the software in irb:
29
+
30
+ irb prompt> load 'lib/sudoku_bardi.rb'
31
+ irb prompt> load 'spec/support/sudoku_fixture.rb'
32
+ irb prompt> include SudokuBardi
33
+ irb prompt> include SudokuFixture
34
+ irb prompt> s = Solver.new EXAMPLE2
35
+ irb prompt> s.take 1
36
+
37
+ After -- s.take 1 -- one will see a listing of an array of size 2. The 0 element is an array of operations made to get the solution. The 1 element is the solution.
38
+
39
+ [75, SudokuBardi::Five] means assign Five to square 75.
40
+ ["start"] ends the operations to construct the puzzle.
41
+ ["mark", 2, [SudokuBardi::Seven, SudokuBardi::Two], 1]
42
+ shows a backtrack mark for square 2. That type of thing
43
+ occurs when there is no longer any square with only one
44
+ possible value. The solver had to try Seven and Two to
45
+ get all possible solutions. The 1 points at Two as the
46
+ current value chosen.
47
+
48
+ 0 1 2 3 4 5 6 7 8
49
+ 9 10 11 12 13 14 15 16 17
50
+ 18 19 20 21 22 23 24 25 26
51
+ 27 28 29 30 31 32 33 34 35
52
+ 36 37 38 39 40 41 42 43 44
53
+ 45 46 47 48 49 50 51 52 53
54
+ 54 55 56 57 58 59 60 61 62
55
+ 63 64 65 66 67 68 69 70 71
56
+ 72 73 74 75 76 77 78 79 80
57
+
58
+ ## Requirements
59
+
60
+ Most likely any recent version of 1.9 ruby works.
61
+
62
+ ## Test with rspec ~> 2.11, rspec -fd
63
+
64
+ Try the software in irb:
65
+
66
+ irb prompt> load 'spec/support/sudoku_bardi_test.rb'
67
+ irb prompt> load 'spec/support/sudoku_fixture.rb'
68
+
69
+ Note, to find the gem installation directory:
70
+
71
+ irb prompt> require 'sudoku_bardi'
72
+ irb prompt> $".grep(/sudoku_bardi/)[0]
73
+ irb prompt> Sudoku::Solver.new.method(:each).source_location
74
+ irb prompt> exit
75
+
76
+ ## License
77
+
78
+ Copyright (c) 2013 Bardi Einarsson. Released under the MIT License. See the [LICENSE][license] file for further details.
79
+
80
+ [license]: https://github.com/bardibardi/sudoku_bardi/blob/master/LICENSE.md
@@ -0,0 +1,272 @@
1
+ module SudokuBardi
2
+
3
+ # row, column, box
4
+ RCB = [
5
+ [0,0,0],[0,1,0],[0,2,0],[0,3,1],[0,4,1],[0,5,1],[0,6,2],[0,7,2],[0,8,2],
6
+ [1,0,0],[1,1,0],[1,2,0],[1,3,1],[1,4,1],[1,5,1],[1,6,2],[1,7,2],[1,8,2],
7
+ [2,0,0],[2,1,0],[2,2,0],[2,3,1],[2,4,1],[2,5,1],[2,6,2],[2,7,2],[2,8,2],
8
+ [3,0,3],[3,1,3],[3,2,3],[3,3,4],[3,4,4],[3,5,4],[3,6,5],[3,7,5],[3,8,5],
9
+ [4,0,3],[4,1,3],[4,2,3],[4,3,4],[4,4,4],[4,5,4],[4,6,5],[4,7,5],[4,8,5],
10
+ [5,0,3],[5,1,3],[5,2,3],[5,3,4],[5,4,4],[5,5,4],[5,6,5],[5,7,5],[5,8,5],
11
+ [6,0,6],[6,1,6],[6,2,6],[6,3,7],[6,4,7],[6,5,7],[6,6,8],[6,7,8],[6,8,8],
12
+ [7,0,6],[7,1,6],[7,2,6],[7,3,7],[7,4,7],[7,5,7],[7,6,8],[7,7,8],[7,8,8],
13
+ [8,0,6],[8,1,6],[8,2,6],[8,3,7],[8,4,7],[8,5,7],[8,6,8],[8,7,8],[8,8,8]
14
+ ]
15
+
16
+ class Piece; end
17
+ class One < Piece; end
18
+ class Two < Piece; end
19
+ class Three < Piece; end
20
+ class Four < Piece; end
21
+ class Five < Piece; end
22
+ class Six < Piece; end
23
+ class Seven < Piece; end
24
+ class Eight < Piece; end
25
+ class Nine < Piece; end
26
+
27
+ TO_S = { nil => '.',
28
+ One => '1', Two => '2', Three => '3',
29
+ Four => '4', Five => '5', Six => '6',
30
+ Seven => '7', Eight => '8', Nine => '9'
31
+ }
32
+
33
+ NINE = [One, Two, Three, Four, Five, Six, Seven, Eight, Nine]
34
+
35
+ class Solver
36
+
37
+ include Enumerable
38
+
39
+ def self.piece_from_char c
40
+ i = c.bytes.first - 49 # 49 == '0'.bytes.first + 1
41
+ return nil if 0 > i
42
+ NINE[i]
43
+ end
44
+
45
+ def self.constraint c
46
+ a = []
47
+ c.size.times do
48
+ a << c.dup
49
+ end
50
+ a
51
+ end
52
+
53
+ def initialize board_str = ''
54
+ start
55
+ process board_str
56
+ push ['start']
57
+ end
58
+
59
+ def start
60
+ @board = [nil] * (NINE.size * NINE.size)
61
+ @rows = self.class.constraint NINE
62
+ @columns = self.class.constraint NINE
63
+ @boxes = self.class.constraint NINE
64
+ @ops = []
65
+ end
66
+
67
+ def board_to_s
68
+ a = @board.map do |piece|
69
+ TO_S[piece]
70
+ end
71
+ a.join ''
72
+ end
73
+
74
+ def possible i
75
+ r, c, b = RCB[i]
76
+ @rows[r] & @columns[c] & @boxes[b]
77
+ end
78
+
79
+ def do_op op
80
+ i, piece = op
81
+ r, c, b = RCB[i]
82
+ @rows[r] -= [piece]
83
+ @columns[c] -= [piece]
84
+ @boxes[b] -= [piece]
85
+ @board[i] = piece
86
+ op
87
+ end
88
+
89
+ def undo_op op
90
+ i, piece = op
91
+ r, c, b = RCB[i]
92
+ @rows[r] += [piece]
93
+ @columns[c] += [piece]
94
+ @boxes[b] += [piece]
95
+ @board[i] = nil
96
+ op
97
+ end
98
+
99
+ def pop_start
100
+ @ops.pop
101
+ end
102
+
103
+ def push_start
104
+ push ['start']
105
+ end
106
+
107
+ def pop
108
+ return nil if 'start' == @ops[-1][0]
109
+ @ops.pop
110
+ end
111
+
112
+ def push op
113
+ @ops << op
114
+ end
115
+
116
+ def process_str str_as_ops
117
+ ops = []
118
+ str_as_ops.each_char.with_index do |char, i|
119
+ piece = self.class.piece_from_char char
120
+ if piece
121
+ ps = possible i
122
+ raise "impossible #{char} at #{i}" unless ps.include? piece
123
+ push do_op([i, piece])
124
+ end
125
+ end
126
+ ops
127
+ end
128
+
129
+ def play ops
130
+ ops.each do |op|
131
+ push do_op(op)
132
+ end
133
+ end
134
+
135
+ def process board_str
136
+ play process_str(board_str)
137
+ end
138
+
139
+ def single i
140
+ processed_single = false
141
+ backtrack = false
142
+ ps = possible i
143
+ if 0 == ps.length
144
+ backtrack = true
145
+ end
146
+ if 1 == ps.length
147
+ processed_single = true
148
+ push do_op([i, ps[0]])
149
+ end
150
+ [processed_single, backtrack]
151
+ end
152
+
153
+ def singles
154
+ had_singles = true
155
+ finished = true
156
+ while had_singles
157
+ finished = true
158
+ single_found = false
159
+ @board.each_with_index do |piece, i|
160
+ if !piece
161
+ finished = false
162
+ processed_single, backtrack = single i
163
+ single_found ||= processed_single
164
+ if backtrack
165
+ push ['backtrack', i]
166
+ return finished
167
+ end
168
+ end
169
+ end
170
+ had_singles = single_found
171
+ end
172
+ finished
173
+ end
174
+
175
+ def min_choices
176
+ j = nil
177
+ count = 10
178
+ @board.each_with_index do |piece, i|
179
+ if !piece
180
+ len = possible(i).size
181
+ if len < count
182
+ j = i
183
+ count = len
184
+ end
185
+ end
186
+ end
187
+ j
188
+ end
189
+
190
+ def gen_choices
191
+ a = []
192
+ @board.each_with_index do |piece, i|
193
+ if !piece
194
+ ps = possible(i)
195
+ if 1 < ps.size
196
+ a += ps.map {|e| [i, e]}
197
+ end
198
+ end
199
+ end
200
+ a
201
+ end
202
+
203
+ def add_mark i
204
+ push ['mark', i, possible(i), -1]
205
+ end
206
+
207
+ def try
208
+ mark = pop.dup
209
+ mark[3] += 1
210
+ piece = mark[2][mark[3]]
211
+ push mark
212
+ if piece
213
+ push do_op([mark[1], piece])
214
+ else
215
+ push ['backtrack', mark[1]]
216
+ end
217
+ end
218
+
219
+ def do_backtrack
220
+ pop if 'mark' == @ops[-1][0]
221
+ op = pop
222
+ if op
223
+ while 'mark' != op[0]
224
+ undo_op op if op[0].kind_of? Numeric
225
+ op = pop
226
+ return @ops unless op
227
+ end
228
+ end
229
+ push op if op
230
+ end
231
+
232
+ def solve_one
233
+ solved = false
234
+ until solved
235
+ if 'backtrack' == @ops[-1][0]
236
+ pop
237
+ do_backtrack
238
+ if 'start' == @ops[-1][0]
239
+ return nil
240
+ end
241
+ try
242
+ else
243
+ solved = singles
244
+ if !solved
245
+ i = min_choices
246
+ add_mark i
247
+ try
248
+ end
249
+ end
250
+ end
251
+ [@ops, board_to_s]
252
+ end
253
+
254
+ def each
255
+ first = true
256
+ finished = false
257
+ until finished
258
+ push ['backtrack', 81] unless first
259
+ first = false
260
+ result = solve_one
261
+ if result
262
+ yield [result[0].dup, result[1]]
263
+ else
264
+ finished = true
265
+ end
266
+ end
267
+ end
268
+
269
+ end # Solver
270
+
271
+ end # SudokuBardi
272
+
@@ -0,0 +1,18 @@
1
+ require 'support/no_should_rspec'
2
+ require 'support/sudoku_bardi_test'
3
+ require 'support/sudoku_fixture'
4
+ include SudokuFixture
5
+
6
+ describe SudokuBardi::Solver do
7
+
8
+ it "should solve puzzles with unique solutions" do
9
+ expect(SudokuBardi.unique_solution?(EXAMPLE, SOLUTION)).to be_true
10
+ expect(SudokuBardi.unique_solution?(EXAMPLE2, SOLUTION2)).to be_true
11
+ expect(SudokuBardi.unique_solution?(EXAMPLE3, SOLUTION3)).to be_true
12
+ end
13
+
14
+ it "should solve puzzles with multiple solutions" do
15
+ expect(SudokuBardi.empty_puzzle_has_at_least_100_solutions?).to be_true
16
+ end
17
+
18
+ end
@@ -0,0 +1,9 @@
1
+ RSpec.configure do |config|
2
+
3
+ # rspec 2.11 turn off should monkey patching
4
+ config.expect_with :rspec do |c|
5
+ c.syntax = :expect
6
+ end
7
+
8
+ end
9
+
@@ -0,0 +1,19 @@
1
+ require_relative '../../lib/sudoku_bardi'
2
+
3
+ module SudokuBardi
4
+
5
+ def self.unique_solution? puzzle_board, solution_board
6
+ s = Solver.new puzzle_board
7
+ solutions = s.take 2
8
+ return nil unless 1 == solutions.size
9
+ solutions[0][1] == solution_board
10
+ end
11
+
12
+ def self.empty_puzzle_has_at_least_100_solutions?
13
+ s = Solver.new
14
+ solutions = s.take 100
15
+ 100 == solutions.size
16
+ end
17
+
18
+ end # SudokuBardi
19
+
@@ -0,0 +1,76 @@
1
+ module SudokuFixture
2
+
3
+ EXAMPLE = %w(
4
+ ...3...1.
5
+ 8...167.2
6
+ 1....93..
7
+ 7.3...645
8
+ 5.68.49.7
9
+ 294...8.1
10
+ ..17....8
11
+ 3.792...4
12
+ .6...1...
13
+ ).join ''
14
+
15
+ SOLUTION = %w(
16
+ 672385419
17
+ 839416752
18
+ 145279386
19
+ 783192645
20
+ 516834927
21
+ 294657831
22
+ 421763598
23
+ 357928164
24
+ 968541273
25
+ ).join ''
26
+
27
+ EXAMPLE2 = %w(
28
+ .....8..6
29
+ 9.17.53.4
30
+ .....4.1.
31
+ 1..94..5.
32
+ 49..5..67
33
+ .2..71..3
34
+ .3.4.....
35
+ 2.53.97.1
36
+ 7..5.....
37
+ ).join ''
38
+
39
+ SOLUTION2 = %w(
40
+ 342198576
41
+ 961725384
42
+ 857634912
43
+ 173946258
44
+ 498253167
45
+ 526871493
46
+ 639417825
47
+ 285369741
48
+ 714582639
49
+ ).join ''
50
+
51
+ EXAMPLE3 = %w(
52
+ 68.35....
53
+ ....1..2.
54
+ ..7.6.9..
55
+ 1........
56
+ 3.5.9.1.2
57
+ ........9
58
+ ..3.2.8..
59
+ .6..7....
60
+ ....45.31
61
+ ).join ''
62
+
63
+ SOLUTION3 = %w(
64
+ 689352417
65
+ 534917628
66
+ 217468953
67
+ 192584376
68
+ 345796182
69
+ 876231549
70
+ 753129864
71
+ 461873295
72
+ 928645731
73
+ ).join ''
74
+
75
+ end
76
+
@@ -0,0 +1,27 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'sudoku_bardi'
3
+ s.version = '0.1.0'
4
+ s.date = '2013-05-23'
5
+ s.summary = "Sudoku solver, validator and generator"
6
+ s.description = "sudoku_bardi (see websudoku.com) is a set of tools for solving, validating and generating Sudoku puzzles."
7
+
8
+ s.authors = ['Bardi Einarsson']
9
+ s.email = ['bardi@hotmail.com']
10
+ s.homepage = 'https://github.com/bardibardi/sudoku_bardi'
11
+ s.required_ruby_version = '>= 1.9.2'
12
+ s.add_development_dependency('rspec', '~> 2.11')
13
+
14
+ s.files = %w(
15
+ .gitignore
16
+ Gemfile
17
+ LICENSE.md
18
+ README.md
19
+ sudoku_bardi.gemspec
20
+ lib/sudoku_bardi.rb
21
+ spec/sudoku_bardi_spec.rb
22
+ spec/support/no_should_rspec.rb
23
+ spec/support/sudoku_bardi_test.rb
24
+ spec/support/sudoku_fixture.rb
25
+ )
26
+ end
27
+
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sudoku_bardi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bardi Einarsson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.11'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.11'
30
+ description: sudoku_bardi (see websudoku.com) is a set of tools for solving, validating
31
+ and generating Sudoku puzzles.
32
+ email:
33
+ - bardi@hotmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE.md
41
+ - README.md
42
+ - sudoku_bardi.gemspec
43
+ - lib/sudoku_bardi.rb
44
+ - spec/sudoku_bardi_spec.rb
45
+ - spec/support/no_should_rspec.rb
46
+ - spec/support/sudoku_bardi_test.rb
47
+ - spec/support/sudoku_fixture.rb
48
+ homepage: https://github.com/bardibardi/sudoku_bardi
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: 1.9.2
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.25
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Sudoku solver, validator and generator
72
+ test_files: []