sudoku_bardi 0.1.0

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