sudoku-solver 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Dorian Sarnowski <dorian.sarnowski@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ == sudoku
2
+
3
+ You should document your project here.
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'sudoku-solver'
10
+ s.version = '0.1.1'
11
+ s.has_rdoc = true
12
+ s.extra_rdoc_files = ['README.md', 'LICENSE']
13
+ s.summary = 'sudoku solver library'
14
+ s.description = 'set of classes to solve sudoku'
15
+ s.author = 'Dorian Sarnowski'
16
+ s.email = 'dorian.sarnowski@gmail.com'
17
+ s.homepage = 'https://github.com/dorians/sudoku'
18
+ s.executables = ['solve-sudoku']
19
+ s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
20
+ s.require_path = "lib"
21
+ s.bindir = "bin"
22
+ end
23
+
24
+ Rake::GemPackageTask.new(spec) do |p|
25
+ p.gem_spec = spec
26
+ p.need_tar = true
27
+ p.need_zip = true
28
+ end
29
+
30
+ Rake::RDocTask.new do |rdoc|
31
+ files =['README.md', 'LICENSE', 'lib/**/*.rb']
32
+ rdoc.rdoc_files.add(files)
33
+ rdoc.main = "README.md" # page to start on
34
+ rdoc.title = "sudoku Docs"
35
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
36
+ rdoc.options << '--line-numbers'
37
+ end
38
+
39
+ Rake::TestTask.new do |t|
40
+ t.test_files = FileList['test/**/*.rb']
41
+ end
data/bin/solve-sudoku ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ NAME = $0
5
+
6
+ require 'sudoku_solver'
7
+
8
+ begin
9
+ input = ARGV.first
10
+ data = open(input).read
11
+ rescue
12
+ puts "#{NAME}: missing file operand"
13
+ #puts "Try `#{NAME} --help` for more information."
14
+ else
15
+ begin
16
+ sudoku = Sudoku.new(data)
17
+ solver = SudokuSolver.new sudoku
18
+ result = solver.solve
19
+
20
+ if result.correct?
21
+ puts result
22
+ else
23
+ puts 'Oh no! Something went wrong'
24
+ end
25
+ rescue
26
+ end
27
+ end
data/lib/sudoku.rb ADDED
@@ -0,0 +1,88 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'sudoku_item.rb'
4
+
5
+ class Sudoku
6
+
7
+ include Enumerable
8
+
9
+ def initialize data
10
+ # set up data container (9x9 array)
11
+ @data = Array.new(9) { Array.new(9) }
12
+
13
+ # convert insert data into one-dimensional array
14
+ data = data.scan(/\d| /)
15
+
16
+ if data.size == 81
17
+ # split array into 9-elements arrays
18
+ data.each_index do |i|
19
+ @data[i/9][i%9] = SudokuItem.new(self, data.at(i))
20
+ end
21
+ end
22
+ end
23
+
24
+ def clone
25
+ Sudoku.new self.to_s
26
+ end
27
+
28
+ def at x, y
29
+ @data.at(y).at(x)
30
+ end
31
+
32
+ def set x, y, value
33
+ at(x, y).set(value) unless at(x, y).related.any? {|item| item.to_i == value }
34
+ end
35
+
36
+ def count_set
37
+ count {|item| item.set?}
38
+ end
39
+
40
+ def groups
41
+ boxes + horizontals + verticals
42
+ end
43
+
44
+ def boxes
45
+ (group_by {|item| (item.x / 3).to_s + (item.y / 3).to_s }).values
46
+ end
47
+
48
+ def horizontals
49
+ @data
50
+ end
51
+
52
+ def verticals
53
+ @data.transpose
54
+ end
55
+
56
+ def solved?
57
+ all? {|item| item.set?}
58
+ end
59
+
60
+ def each
61
+ each_with_position {|item, x, y| yield item}
62
+ end
63
+
64
+ def each_with_position
65
+ @data.each_with_index {|row, y| row.each_with_index {|item, x| yield item, x, y}}
66
+ end
67
+
68
+ def to_s
69
+ # output schemas
70
+ break_line = '+---+---+---+'
71
+ data_line = "|%s%s%s|%s%s%s|%s%s%s|"
72
+
73
+ # initialize the output variable as an array
74
+ result = [break_line]
75
+
76
+ @data.each_with_index do |row, i|
77
+ # add formated data line to output variable
78
+ result.push sprintf(data_line, *row)
79
+
80
+ # add a break line to the output variable if nedded
81
+ result.push break_line if (i.succ % 3).zero?
82
+ end
83
+
84
+ # return output variable with EOL symbol between each element
85
+ result.join("\n")
86
+ end
87
+
88
+ end
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ class SudokuItem
4
+
5
+ def initialize sudoku, value
6
+ @sudoku = sudoku
7
+ set value
8
+ end
9
+
10
+ def x
11
+ position.first
12
+ end
13
+
14
+ def y
15
+ position.last
16
+ end
17
+
18
+ def set value
19
+ @value = value.to_i.zero? ? nil : value.to_i unless set?
20
+ end
21
+
22
+ def position
23
+ @position || @sudoku.each_with_position do |element, x, y|
24
+ return (@position = x.to_i, y.to_i) if element == self
25
+ end
26
+ end
27
+
28
+ def related
29
+ (related_in_box + related_vertical + related_horizontal).uniq
30
+ end
31
+
32
+ def related_in_box
33
+ @sudoku.find_all {|item| (same_box? item) && (item != self)}
34
+ end
35
+
36
+ def related_vertical
37
+ @sudoku.find_all {|item| (item.x == self.x) && (item != self)}
38
+ end
39
+
40
+ def related_horizontal
41
+ @sudoku.find_all {|item| (item.y == self.y) && (item != self)}
42
+ end
43
+
44
+ def same_box? item
45
+ (self.x / 3) == (item.x / 3) && (self.y / 3) == (item.y / 3)
46
+ end
47
+
48
+ def set?
49
+ not @value.nil?
50
+ end
51
+
52
+ def to_i
53
+ @value.to_i
54
+ end
55
+
56
+ def to_s
57
+ set? ? @value.to_s : ' '
58
+ end
59
+
60
+ end
@@ -0,0 +1,46 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'sudoku'
4
+
5
+ class SudokuResult
6
+
7
+ include Enumerable
8
+
9
+ attr_reader :depth, :forks
10
+
11
+ def initialize depth
12
+ @results = []
13
+ @depth = depth
14
+ @forks = 0
15
+ end
16
+
17
+ def each &block
18
+ @results.each &block
19
+ end
20
+
21
+ def << sudoku
22
+ @results << sudoku
23
+ self
24
+ end
25
+
26
+ def correct?
27
+ @results.count == 1
28
+ end
29
+
30
+ def many_solutions?
31
+ @results.count > 1
32
+ end
33
+
34
+ def to_s
35
+ @results.empty? ? '' : @results.first.to_s
36
+ end
37
+
38
+ def merge other
39
+ other.each do |sudoku|
40
+ self.<< sudoku
41
+ end
42
+ @depth = [@depth, other.depth].max
43
+ @forks += other.forks.succ
44
+ end
45
+
46
+ end
@@ -0,0 +1,139 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'sudoku'
4
+ require 'sudoku_result'
5
+
6
+ class SudokuSolver
7
+
8
+ def initialize sudoku
9
+ @sudoku = sudoku.clone
10
+ @depth = 0
11
+
12
+ initialize_capabilities
13
+ end
14
+
15
+ def clone
16
+ solver = super
17
+
18
+ solver.sudoku = @sudoku.clone
19
+ solver.capabilities = Marshal.load(Marshal.dump(@capabilities))
20
+ @depth += 1
21
+
22
+ solver
23
+ end
24
+
25
+ def solve
26
+ result = SudokuResult.new @depth
27
+
28
+ while (solve_method1 || solve_method2) do
29
+ return result unless correct?
30
+ end
31
+
32
+ if @sudoku.solved?
33
+ result << @sudoku
34
+ else
35
+ element = find_best_element_to_random_solve
36
+
37
+ @capabilities[element.y][element.x].each do |random|
38
+ solver = self.clone
39
+ solver.set(element.x, element.y, random)
40
+ result.merge solver.solve
41
+ end
42
+ end
43
+
44
+ result
45
+ end
46
+
47
+ protected
48
+
49
+ attr_accessor :sudoku, :capabilities, :depth
50
+
51
+ def set x, y, value
52
+ # delegate a task
53
+ if @sudoku.set x, y, value
54
+ # item was set, so clear capabilities
55
+ @capabilities[y][x].clear
56
+
57
+ # remove set value from capabilities of related items
58
+ @sudoku.at(x, y).related.each do |item|
59
+ @capabilities[item.y][item.x] -= [value]
60
+ end
61
+ end
62
+ end
63
+
64
+ def correct?
65
+ @sudoku.each do |item|
66
+ return false if (not item.set?) && @capabilities[item.y][item.x].empty?
67
+ end
68
+ true
69
+ end
70
+
71
+ private
72
+
73
+ def solve_method1
74
+ @capabilities.each_with_index do |row, y|
75
+ row.each_with_index do |element, x|
76
+ if element.size == 1
77
+ return set x, y, element.first
78
+ end
79
+ end
80
+ end
81
+ false
82
+ end
83
+
84
+ def solve_method2
85
+ @sudoku.groups.each do |group|
86
+ set_digits = (group.find_all {|item| item.set?}).map {|item| item.to_i}
87
+ capabilities_digits = (1..9).to_a - set_digits
88
+
89
+ capabilities_digits.each do |digit|
90
+ capabilities_items = group.find_all do |item|
91
+ @capabilities[item.y][item.x].include? digit
92
+ end
93
+ if capabilities_items.size == 1
94
+ return set capabilities_items.first.x, capabilities_items.first.y, digit
95
+ end
96
+ end
97
+ end
98
+ false
99
+ end
100
+
101
+ def find_best_element_to_random_solve
102
+ min_element = nil
103
+
104
+ @capabilities.each_with_index do |row, y|
105
+ row.each_with_index do |capabilities, x|
106
+ element = @sudoku.at(x, y)
107
+ current_count = capabilities.count
108
+ smallest_count = min_element && @capabilities[min_element.y][min_element.x].count
109
+
110
+ if current_count > 1
111
+ if current_count == 2
112
+ return element
113
+ elsif smallest_count.nil? || current_count < smallest_count
114
+ min_element = element
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ min_element
121
+ end
122
+
123
+ def initialize_capabilities
124
+ # each item of sudoku can be any digit from 1 to 9 at the beginning
125
+ @capabilities = Array.new(9) {Array.new(9) { (1..9).to_a }}
126
+
127
+ @sudoku.each do |item|
128
+ # if item is set, then capabilities should be empty
129
+ @capabilities[item.y][item.x].clear if item.set?
130
+
131
+ # get related as array of integers
132
+ related = item.related.collect {|element| element.to_i}
133
+
134
+ # any digit from related shouldn't be one of capabilities
135
+ @capabilities[item.y][item.x] -= related
136
+ end
137
+ end
138
+
139
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sudoku-solver
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Dorian Sarnowski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-13 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: set of classes to solve sudoku
23
+ email: dorian.sarnowski@gmail.com
24
+ executables:
25
+ - solve-sudoku
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.md
30
+ - LICENSE
31
+ files:
32
+ - LICENSE
33
+ - README.md
34
+ - Rakefile
35
+ - bin/solve-sudoku
36
+ - lib/sudoku_result.rb
37
+ - lib/sudoku_item.rb
38
+ - lib/sudoku.rb
39
+ - lib/sudoku_solver.rb
40
+ has_rdoc: true
41
+ homepage: https://github.com/dorians/sudoku
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: sudoku solver library
74
+ test_files: []
75
+