sudokusolver 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.
- data/COPYING +1 -0
- data/Changes.rdoc +1 -0
- data/LICENSE +22 -0
- data/README.rdoc +20 -0
- data/Rakefile +256 -0
- data/lib/sudokusolver.rb +197 -0
- data/test/driver.rb +10 -0
- data/test/tc_sudoku.rb +53 -0
- metadata +57 -0
data/COPYING
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
See the file called LICENSE
|
data/Changes.rdoc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2007-04-09 Version 0.1 => initial release
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2007
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
This sofware was translated from the python source code
|
2
|
+
obtained from Peter Norvig's website:
|
3
|
+
|
4
|
+
http://www.norvig.com/sudoku.html
|
5
|
+
http://www.norvig.com/sudo.py
|
6
|
+
|
7
|
+
Thank you to Peter Norvig for the original python source code,
|
8
|
+
algorithms and exceptionally clear explanations.
|
9
|
+
|
10
|
+
example:
|
11
|
+
========
|
12
|
+
|
13
|
+
require 'sudoku_solver'
|
14
|
+
|
15
|
+
# The puzzle representation is simply the 9 rows of the Sudoku grid stringed together
|
16
|
+
# from top to bottom (periods representing blank squares)
|
17
|
+
|
18
|
+
puzzle = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......"
|
19
|
+
s = SudokuSolver.new
|
20
|
+
s.print_grid(s.search(s.parse_grid(puzzle)))
|
data/Rakefile
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# Rakefile
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/clean"
|
4
|
+
require "rake/rdoctask"
|
5
|
+
require "rake/gempackagetask"
|
6
|
+
|
7
|
+
#---
|
8
|
+
# The name of your project
|
9
|
+
PROJECT = "SudokuSolver"
|
10
|
+
|
11
|
+
# Your name, used in packaging.
|
12
|
+
MY_NAME = "Martin-Louis Bright"
|
13
|
+
|
14
|
+
# Your email address, used in packaging.
|
15
|
+
MY_EMAIL = "mlbright@gmail.com"
|
16
|
+
|
17
|
+
# Short summary of your project, used in packaging.
|
18
|
+
PROJECT_SUMMARY = "Commandline program and library for solving Sudoku puzzles"
|
19
|
+
|
20
|
+
# The project's package name (as opposed to its display name). Used for
|
21
|
+
# RubyForge connectivity and packaging.
|
22
|
+
UNIX_NAME = "sudokusolver"
|
23
|
+
|
24
|
+
# Your RubyForge user name.
|
25
|
+
RUBYFORGE_USER = ENV["RUBYFORGE_USER"] || "mlbright"
|
26
|
+
|
27
|
+
# Directory on RubyForge where your website's files should be uploaded.
|
28
|
+
WEBSITE_DIR = "sudokusolver"
|
29
|
+
|
30
|
+
# Output directory for the rdoc html files.
|
31
|
+
# If you don't have a custom homepage, and want to use the RDoc
|
32
|
+
# index.html as homepage, just set it to WEBSITE_DIR.
|
33
|
+
RDOC_HTML_DIR = "#{WEBSITE_DIR}/rdoc"
|
34
|
+
|
35
|
+
#---
|
36
|
+
# Variable settings for extension support.
|
37
|
+
EXT_DIR = "ext"
|
38
|
+
HAVE_EXT = File.directory?(EXT_DIR)
|
39
|
+
EXTCONF_FILES = FileList["#{EXT_DIR}/**/extconf.rb"]
|
40
|
+
EXT_SOURCES = FileList["#{EXT_DIR}/**/*.{c,h}"]
|
41
|
+
# Eventually add other files from EXT_DIR, like "MANIFEST"
|
42
|
+
EXT_DIST_FILES = EXT_SOURCES + EXTCONF_FILES
|
43
|
+
|
44
|
+
#---
|
45
|
+
REQUIRE_PATHS = ["lib"]
|
46
|
+
REQUIRE_PATHS << EXT_DIR if HAVE_EXT
|
47
|
+
$LOAD_PATH.concat(REQUIRE_PATHS)
|
48
|
+
# This library file defines the MyProject::VERSION constant.
|
49
|
+
require "#{UNIX_NAME}"
|
50
|
+
PROJECT_VERSION = eval("#{PROJECT}::VERSION") # e.g. "1.0.2"
|
51
|
+
|
52
|
+
#---
|
53
|
+
# Clobber object files and Makefiles generated by extconf.rb.
|
54
|
+
CLOBBER.include("#{EXT_DIR}/**/*.{so,dll,o}", "#{EXT_DIR}/**/Makefile")
|
55
|
+
# Clobber .config generated by setup.rb.
|
56
|
+
CLOBBER.include(".config")
|
57
|
+
|
58
|
+
#---
|
59
|
+
# Options common to RDocTask AND Gem::Specification.
|
60
|
+
# The --main argument specifies which file appears on the index.html page
|
61
|
+
GENERAL_RDOC_OPTS = {
|
62
|
+
"--title" => "#{PROJECT} API documentation",
|
63
|
+
"--main" => "README.rdoc"
|
64
|
+
}
|
65
|
+
|
66
|
+
# Additional RDoc formatted files, besides the Ruby source files.
|
67
|
+
RDOC_FILES = FileList["README.rdoc", "Changes.rdoc"]
|
68
|
+
# Remove the following line if you don't want to extract RDoc from
|
69
|
+
# the extension C sources.
|
70
|
+
RDOC_FILES.include(EXT_SOURCES)
|
71
|
+
|
72
|
+
# Ruby library code.
|
73
|
+
LIB_FILES = FileList["lib/**/*.rb"]
|
74
|
+
|
75
|
+
# Filelist with Test::Unit test cases.
|
76
|
+
TEST_FILES = FileList["test/**/tc_*.rb"]
|
77
|
+
|
78
|
+
# Executable scripts, all non-garbage files under bin/.
|
79
|
+
BIN_FILES = FileList["bin/*"]
|
80
|
+
|
81
|
+
# This filelist is used to create source packages.
|
82
|
+
# Include all Ruby and RDoc files.
|
83
|
+
DIST_FILES = FileList["**/*.rb", "**/*.rdoc"]
|
84
|
+
DIST_FILES.include("Rakefile", "COPYING", "LICENSE")
|
85
|
+
DIST_FILES.include(BIN_FILES)
|
86
|
+
DIST_FILES.include("data/**/*", "test/data/**/*")
|
87
|
+
DIST_FILES.include("#{WEBSITE_DIR}/**/*.{html,css}", "man/*.[0-9]")
|
88
|
+
# Don't package files which are autogenerated by RDocTask
|
89
|
+
DIST_FILES.exclude(/^(\.\/)?#{RDOC_HTML_DIR}(\/|$)/)
|
90
|
+
# Include extension source files.
|
91
|
+
DIST_FILES.include(EXT_DIST_FILES)
|
92
|
+
# Don't package temporary files, perhaps created by tests.
|
93
|
+
DIST_FILES.exclude("**/temp_*", "**/*.tmp")
|
94
|
+
# Don't get into recursion...
|
95
|
+
DIST_FILES.exclude(/^(\.\/)?pkg(\/|$)/)
|
96
|
+
|
97
|
+
#---
|
98
|
+
# Run the tests if rake is invoked without arguments.
|
99
|
+
task "default" => ["test"]
|
100
|
+
|
101
|
+
test_task_name = HAVE_EXT ? "run-tests" : "test"
|
102
|
+
Rake::TestTask.new(test_task_name) do |t|
|
103
|
+
t.test_files = TEST_FILES
|
104
|
+
t.libs = REQUIRE_PATHS
|
105
|
+
end
|
106
|
+
|
107
|
+
#---
|
108
|
+
# Set an environment variable with any configuration options you want to
|
109
|
+
# be passed through to "setup.rb config".
|
110
|
+
CONFIG_OPTS = ENV["CONFIG"]
|
111
|
+
if HAVE_EXT
|
112
|
+
file_create ".config" do
|
113
|
+
ruby "setup.rb config #{CONFIG_OPTS}"
|
114
|
+
end
|
115
|
+
|
116
|
+
desc "Configure and make extension. " +
|
117
|
+
"The CONFIG variable is passed to `setup.rb config'"
|
118
|
+
task "make-ext" => ".config" do
|
119
|
+
# The -q option suppresses messages from setup.rb.
|
120
|
+
ruby "setup.rb -q setup"
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Run tests after making the extension."
|
124
|
+
task "test" do
|
125
|
+
Rake::Task["make-ext"].invoke
|
126
|
+
Rake::Task["run-tests"].invoke
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#---
|
131
|
+
# The "rdoc" task generates API documentation.
|
132
|
+
Rake::RDocTask.new("rdoc") do |t|
|
133
|
+
t.rdoc_files = RDOC_FILES + LIB_FILES
|
134
|
+
t.title = GENERAL_RDOC_OPTS["--title"]
|
135
|
+
t.main = GENERAL_RDOC_OPTS["--main"]
|
136
|
+
t.rdoc_dir = RDOC_HTML_DIR
|
137
|
+
end
|
138
|
+
|
139
|
+
#---
|
140
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
141
|
+
s.name = UNIX_NAME
|
142
|
+
s.version = PROJECT_VERSION
|
143
|
+
s.summary = PROJECT_SUMMARY
|
144
|
+
s.rubyforge_project = UNIX_NAME
|
145
|
+
s.homepage = "http://#{UNIX_NAME}.rubyforge.org/"
|
146
|
+
s.author = MY_NAME
|
147
|
+
s.email = MY_EMAIL
|
148
|
+
s.files = DIST_FILES
|
149
|
+
s.test_files = TEST_FILES
|
150
|
+
s.executables = BIN_FILES.map { |fn| File.basename(fn) }
|
151
|
+
s.has_rdoc = true
|
152
|
+
s.extra_rdoc_files = RDOC_FILES
|
153
|
+
s.rdoc_options = GENERAL_RDOC_OPTS.to_a.flatten
|
154
|
+
if HAVE_EXT
|
155
|
+
s.extensions = EXTCONF_FILES
|
156
|
+
s.require_paths << EXT_DIR
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Now we can generate the package-related tasks.
|
161
|
+
Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
|
162
|
+
pkg.need_zip = true
|
163
|
+
pkg.need_tar = true
|
164
|
+
end
|
165
|
+
|
166
|
+
#---
|
167
|
+
desc "Upload website to RubyForge. " +
|
168
|
+
"scp will prompt for your RubyForge password."
|
169
|
+
task "publish-website" => ["rdoc"] do
|
170
|
+
rubyforge_path = "/var/www/gforge-projects/#{UNIX_NAME}/"
|
171
|
+
sh "scp -r #{WEBSITE_DIR}/* " +
|
172
|
+
"#{RUBYFORGE_USER}@rubyforge.org:#{rubyforge_path}",
|
173
|
+
:verbose => true
|
174
|
+
end
|
175
|
+
|
176
|
+
#---
|
177
|
+
task "rubyforge-setup" do
|
178
|
+
unless File.exist?(File.join(ENV["HOME"], ".rubyforge"))
|
179
|
+
puts "rubyforge will ask you to edit its config.yml now."
|
180
|
+
puts "Please set the `username' and `password' entries"
|
181
|
+
puts "to your RubyForge username and RubyForge password!"
|
182
|
+
puts "Press ENTER to continue."
|
183
|
+
$stdin.gets
|
184
|
+
sh "rubyforge setup", :verbose => true
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
task "rubyforge-login" => ["rubyforge-setup"] do
|
189
|
+
# Note: We assume that username and password were set in
|
190
|
+
# rubyforge's config.yml.
|
191
|
+
sh "rubyforge login", :verbose => true
|
192
|
+
end
|
193
|
+
|
194
|
+
task "publish-packages" => ["package", "rubyforge-login"] do
|
195
|
+
# Upload packages under pkg/ to RubyForge
|
196
|
+
# This task makes some assumptions:
|
197
|
+
# * You have already created a package on the "Files" tab on the
|
198
|
+
# RubyForge project page. See pkg_name variable below.
|
199
|
+
# * You made entries under package_ids and group_ids for this
|
200
|
+
# project in rubyforge's config.yml. If not, eventually read
|
201
|
+
# "rubyforge --help" and then run "rubyforge setup".
|
202
|
+
pkg_name = ENV["PKG_NAME"] || UNIX_NAME
|
203
|
+
cmd = "rubyforge add_release #{UNIX_NAME} #{pkg_name} " +
|
204
|
+
"#{PROJECT_VERSION} #{UNIX_NAME}-#{PROJECT_VERSION}"
|
205
|
+
cd "pkg" do
|
206
|
+
sh(cmd + ".gem", :verbose => true)
|
207
|
+
sh(cmd + ".tgz", :verbose => true)
|
208
|
+
sh(cmd + ".zip", :verbose => true)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#---
|
213
|
+
# The "prepare-release" task makes sure your tests run, and then generates
|
214
|
+
# files for a new release.
|
215
|
+
desc "Run tests, generate RDoc and create packages."
|
216
|
+
task "prepare-release" => ["clobber"] do
|
217
|
+
puts "Preparing release of #{PROJECT} version #{VERSION}"
|
218
|
+
Rake::Task["test"].invoke
|
219
|
+
Rake::Task["rdoc"].invoke
|
220
|
+
Rake::Task["package"].invoke
|
221
|
+
end
|
222
|
+
|
223
|
+
# The "publish" task is the overarching task for the whole project. It
|
224
|
+
# builds a release and then publishes it to RubyForge.
|
225
|
+
desc "Publish new release of #{PROJECT}"
|
226
|
+
task "publish" => ["prepare-release"] do
|
227
|
+
puts "Uploading documentation..."
|
228
|
+
Rake::Task["publish-website"].invoke
|
229
|
+
puts "Checking for rubyforge command..."
|
230
|
+
`rubyforge --help`
|
231
|
+
if $? == 0
|
232
|
+
puts "Uploading packages..."
|
233
|
+
Rake::Task["publish-packages"].invoke
|
234
|
+
puts "Release done!"
|
235
|
+
else
|
236
|
+
puts "Can't invoke rubyforge command."
|
237
|
+
puts "Either install rubyforge with 'gem install rubyforge'"
|
238
|
+
puts "and retry or upload the package files manually!"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
#---
|
243
|
+
# $ rake -T
|
244
|
+
# rake clean # Remove any temporary products.
|
245
|
+
# rake clobber # Remove any generated file.
|
246
|
+
# rake clobber_package # Remove package products
|
247
|
+
# rake clobber_rdoc # Remove rdoc products
|
248
|
+
# rake package # Build all the packages
|
249
|
+
# rake prepare-release # Run tests, generate RDoc and create packages.
|
250
|
+
# rake publish # Publish new release of MyProject
|
251
|
+
# rake publish-website # Upload website to RubyForge. scp will prompt for your RubyForge password.
|
252
|
+
# rake rdoc # Build the rdoc HTML Files
|
253
|
+
# rake repackage # Force a rebuild of the package files
|
254
|
+
# rake rerdoc # Force a rebuild of the RDOC files
|
255
|
+
# rake test # Run tests for test
|
256
|
+
#---
|
data/lib/sudokusolver.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Translated into ruby from python by Martin-Louis Bright
|
4
|
+
# Algorithm, overall structure and original python source code by Peter Norvig
|
5
|
+
# See http://norvig.com/sudoku.html
|
6
|
+
|
7
|
+
## Throughout this program:
|
8
|
+
## r is a row, e.g. 'A'
|
9
|
+
## c is a column, e.g. '3'
|
10
|
+
## s is a square, e.g. 'A3'
|
11
|
+
## d is a digit, e.g. '9'
|
12
|
+
## u is a unit, e.g. ['A1','B1','C1','D1','E1','F1','G1','H1','I1']
|
13
|
+
## g is a grid, e.g. 81 non-blank chars, e.g. starting with '.18...7...
|
14
|
+
## values is a hash of possible values, e.g. {'A1':'123489', 'A2':'8', ...}
|
15
|
+
|
16
|
+
class SudokuSolver
|
17
|
+
VERSION = "1.0"
|
18
|
+
attr_reader :rows, :cols, :squares, :unitlist, :peers, :units
|
19
|
+
|
20
|
+
def cross(a, b)
|
21
|
+
cp = Array.new # cross product
|
22
|
+
a.each do |x|
|
23
|
+
b.each do |y|
|
24
|
+
cp << x+y
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return cp
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize()
|
31
|
+
@rows = ('A'..'I').to_a
|
32
|
+
@cols = ('1'..'9').to_a
|
33
|
+
@squares = cross(@rows, @cols)
|
34
|
+
@unitlist = Array.new
|
35
|
+
cols.each { |c| @unitlist.push(cross(rows, c)) }
|
36
|
+
rows.each { |r| @unitlist.push(cross(r, cols)) }
|
37
|
+
for rb in ['ABC','DEF','GHI'] do
|
38
|
+
for cb in ['123','456','789'] do
|
39
|
+
@unitlist << cross(rb.split(''),cb.split(''))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@units = Hash.new
|
44
|
+
squares.each do |s|
|
45
|
+
@units[s] = Array.new
|
46
|
+
unitlist.each do |u|
|
47
|
+
u.each do |x|
|
48
|
+
@units[s].push(u) if s == x
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@peers = Hash.new
|
54
|
+
squares.each do |s|
|
55
|
+
@peers[s] = Array.new
|
56
|
+
units[s].each do |u|
|
57
|
+
u.each { |s2| @peers[s] << s2 if s2 != s }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_grid(g)
|
64
|
+
g = g.chomp
|
65
|
+
g = g.split('')
|
66
|
+
values = Hash.new
|
67
|
+
# Initially any square can be anything.
|
68
|
+
squares.each { |s| values[s] = "123456789" }
|
69
|
+
for s,d in squares.zip(g)
|
70
|
+
return false unless assign(values, s, d) if d =~ /\d/
|
71
|
+
end
|
72
|
+
return values
|
73
|
+
end
|
74
|
+
|
75
|
+
def assign(values, s, d)
|
76
|
+
values[s].split('').each do |d2|
|
77
|
+
unless d2 == d
|
78
|
+
return false if eliminate(values, s, d2) == false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return values
|
82
|
+
end
|
83
|
+
|
84
|
+
def eliminate(values, s, d)
|
85
|
+
return values unless values[s].include?(d) ## Already eliminated.
|
86
|
+
|
87
|
+
values[s] = values[s].sub(d,'') ## Remove the digit from the string of possibilities
|
88
|
+
## values[s].sub!(d,'') => why doesn't sub!() work?
|
89
|
+
|
90
|
+
return false if values[s].length == 0 ## Contradiction: no more values (no more digits can be assigned)
|
91
|
+
|
92
|
+
## Remove digit from all peers
|
93
|
+
peers[s].each { |s2| return false unless eliminate(values, s2, values[s]) } if values[s].length == 1
|
94
|
+
|
95
|
+
## Assign digit if, by elimination, there is only one square left
|
96
|
+
## in the units for this square that can hold the digit
|
97
|
+
units[s].each do |u|
|
98
|
+
dplaces = Array.new
|
99
|
+
u.each { |s2| dplaces << s2 if values[s2].include?(d) }
|
100
|
+
return false if dplaces.length == 0 # bad
|
101
|
+
return false if assign(values, dplaces[0], d) == false if dplaces.length == 1
|
102
|
+
end
|
103
|
+
return values
|
104
|
+
end
|
105
|
+
|
106
|
+
def search(values)
|
107
|
+
return false if values == false
|
108
|
+
|
109
|
+
solved = true ## assumption
|
110
|
+
squares.each do |s|
|
111
|
+
unless values[s].length == 1
|
112
|
+
solved = false
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
return values if solved == true ## Solved!
|
117
|
+
|
118
|
+
min = 10
|
119
|
+
start = nil
|
120
|
+
squares.each do |s| ## Chose the undetermined square s with the fewest possibilities
|
121
|
+
l = values[s].length
|
122
|
+
if l > 1 && l < min
|
123
|
+
min = l
|
124
|
+
start = s
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
values[start].split('').each do |d|
|
129
|
+
solution = search(assign(values.clone,start,d))
|
130
|
+
return solution unless solution == false
|
131
|
+
end
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
|
135
|
+
def print_grid(values)
|
136
|
+
return if values == false
|
137
|
+
max = 0
|
138
|
+
squares.each { |s| max = values[s].length if values[s].length > max }
|
139
|
+
width = 1 + max
|
140
|
+
a = Array.new
|
141
|
+
3.times do |c|
|
142
|
+
tmp = ""
|
143
|
+
(3*width).times do
|
144
|
+
tmp = tmp + '-'
|
145
|
+
end
|
146
|
+
tmp += "-" if c == 1
|
147
|
+
a.push(tmp)
|
148
|
+
end
|
149
|
+
line = "\n" + a.join('+')
|
150
|
+
|
151
|
+
tmp = ""
|
152
|
+
for r in rows
|
153
|
+
for c in cols
|
154
|
+
tmp = tmp + values[r+c].center(width)
|
155
|
+
if c == '3' or c == '6'
|
156
|
+
tmp = tmp + '| '
|
157
|
+
end
|
158
|
+
end
|
159
|
+
tmp = tmp + line if r == 'C' or r == 'F'
|
160
|
+
tmp = tmp + "\n"
|
161
|
+
end
|
162
|
+
puts tmp + "\n"
|
163
|
+
return values
|
164
|
+
end
|
165
|
+
|
166
|
+
def string_solution(values)
|
167
|
+
solution = ""
|
168
|
+
squares.each do |s|
|
169
|
+
solution += values[s]
|
170
|
+
end
|
171
|
+
return solution
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_solution(solution)
|
175
|
+
values = Hash.new
|
176
|
+
for s,d in squares.zip(solution.split(''))
|
177
|
+
values[s] = d
|
178
|
+
end
|
179
|
+
|
180
|
+
unitlist.each do |u|
|
181
|
+
tmp = Hash.new
|
182
|
+
u.each do |s|
|
183
|
+
tmp[values[s]] = true
|
184
|
+
end
|
185
|
+
return false unless tmp.keys.length == 9
|
186
|
+
end
|
187
|
+
return true
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
## Algorithm by Peter Norvig @ http://www.norvig.com/sudoku.html
|
193
|
+
|
194
|
+
## More constraints:
|
195
|
+
## http://www.scanraid.com/BasicStrategies.htm
|
196
|
+
## http://www.krazydad.com/blog/2005/09/29/an-index-of-sudoku-strategies/
|
197
|
+
## http://www2.warwick.ac.uk/fac/sci/moac/currentstudents/peter_cock/python/sudoku/
|
data/test/driver.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Martin-Louis Bright on 2007-04-10.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 'sudokusolver'
|
7
|
+
|
8
|
+
puzzle = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......"
|
9
|
+
s = SudokuSolver.new
|
10
|
+
s.print_grid(s.search(s.parse_grid(puzzle)))
|
data/test/tc_sudoku.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Martin-Louis Bright on 2007-03-21.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'sudokusolver')
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
class SudokuTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@path = File.join(File.dirname(__FILE__), '..')
|
13
|
+
@easy = File.read(@path + '/test/easy_puzzles.txt').split("\n")
|
14
|
+
@hard = File.read(@path + '/test/top95.txt').split("\n")
|
15
|
+
@s = SudokuSolver.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_empty_puzzle
|
19
|
+
e = "................................................................................."
|
20
|
+
sol = @s.string_solution(@s.search(@s.parse_grid(e)))
|
21
|
+
puts sol
|
22
|
+
assert(@s.check_solution(sol))
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_sanity
|
26
|
+
puts "Easy puzzle (constraint satisfaction only): "
|
27
|
+
puts
|
28
|
+
@s.print_grid(@s.search(@s.parse_grid(@easy[0])))
|
29
|
+
puts "Hard puzzle (constraint satisfaction + search): "
|
30
|
+
puts
|
31
|
+
@s.print_grid(@s.search(@s.parse_grid(@hard[0])))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_easy
|
35
|
+
multiple(@easy)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_hard
|
39
|
+
multiple(@hard)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_bmark_hard
|
43
|
+
@hard.each do |g|
|
44
|
+
puts @s.string_solution(@s.search(@s.parse_grid(g)))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def multiple(puzzles)
|
49
|
+
puzzles.each do |g|
|
50
|
+
assert(@s.check_solution(@s.string_solution(@s.search(@s.parse_grid(g)))))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: sudokusolver
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "1.0"
|
7
|
+
date: 2007-04-15 00:00:00 -04:00
|
8
|
+
summary: Commandline program and library for solving Sudoku puzzles
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: mlbright@gmail.com
|
12
|
+
homepage: http://sudokusolver.rubyforge.org/
|
13
|
+
rubyforge_project: sudokusolver
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Martin-Louis Bright
|
31
|
+
files:
|
32
|
+
- lib/sudokusolver.rb
|
33
|
+
- test/driver.rb
|
34
|
+
- test/tc_sudoku.rb
|
35
|
+
- Changes.rdoc
|
36
|
+
- README.rdoc
|
37
|
+
- Rakefile
|
38
|
+
- COPYING
|
39
|
+
- LICENSE
|
40
|
+
test_files:
|
41
|
+
- test/tc_sudoku.rb
|
42
|
+
rdoc_options:
|
43
|
+
- --title
|
44
|
+
- SudokuSolver API documentation
|
45
|
+
- --main
|
46
|
+
- README.rdoc
|
47
|
+
extra_rdoc_files:
|
48
|
+
- README.rdoc
|
49
|
+
- Changes.rdoc
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
dependencies: []
|
57
|
+
|