nsudoku 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sudoku
2
2
 
3
- This gem solve puzzle game sudoku 9x9
3
+ This gem solves puzzle game sudoku (n^2, n^2), for example (3^2, 3^2) = (9, 9) or (4, 4)
4
4
 
5
5
  ## Installation
6
6
 
@@ -29,15 +29,34 @@ Usage class NSudoku for example:
29
29
  "050906010",
30
30
  "000320908",
31
31
  "000580107",
32
- "000000240"].join
32
+ "000000240"
33
+ ].join
33
34
 
34
- Solve game the sudoku
35
+ Solve the sudoku game
35
36
 
36
- > require 'nsudoku'
37
- > NSudoku.new(SUDOKU).solve #=> "712460850394852671685170420070208596926715384050906712540320968269584137030690245"
38
- > NSudoku.new(SUDOKU).solution #=> "712463859394852671685179423173248596926715384458936712541327968269584137837691245"
37
+ Result which match to all solutions of SUDOKU (if sample has got more than one result)
38
+
39
+ NSudoku.new(SUDOKU).solve #=> "712460850394852671685170420070208596926715384050906712540320968269584137030690245"
40
+
41
+ First correct solution for variable SUDOKU
42
+
43
+ NSudoku.new(SUDOKU).solution #=> "712463859394852671685179423173248596926715384458936712541327968269584137837691245"
44
+
45
+ You can solve sudoku (4, 4)
46
+
47
+ SUDOKU44 = [
48
+ "1030",
49
+ "4000",
50
+ "0140",
51
+ "3001"
52
+ ].join
53
+
54
+ NSudoku.new(SUDOKU44).solution #=> "1234431221433421"
55
+ NSudoku.new(SUDOKU44).solve #=> "1234431221433421"
56
+
57
+ If you see the same results in methods "solution " and "solve", then sudoku has got only one result
39
58
 
40
59
  Check whether your array has got that same values in rows, columns or sub blocks. When your array is correct you should expect result nil.
41
60
  In other situation you should received array of position with that same value.
42
61
 
43
- > NSudoku::Checker.new(SUDOKU).repeat_in #=> nil
62
+ NSudoku::Checker.new(SUDOKU).repeat_in #=> nil
@@ -6,7 +6,6 @@ class NSudoku
6
6
  @sudoku = sudoku
7
7
  @width = Math.sqrt(@sudoku.length).to_i
8
8
  @block_width = Math.sqrt(@width).to_i
9
- # puts @block_width
10
9
  end
11
10
 
12
11
  def correct?
@@ -0,0 +1,168 @@
1
+ # encoding: utf-8
2
+
3
+ class NSudoku
4
+ class Solver
5
+
6
+ attr_accessor :revers
7
+
8
+ def initialize(sudoku)
9
+ @sudoku = sudoku
10
+ @width = Math.sqrt(sudoku.length).to_i
11
+ @block_width = Math.sqrt(@width).to_i
12
+ @revers = create_revers(@sudoku.split(""))
13
+ end
14
+
15
+ # search result of sudoku
16
+ def search
17
+ revers = nil
18
+ while not revers == @revers
19
+ revers = Marshal.load(Marshal.dump(@revers))
20
+ @width.times do |index|
21
+ [:row, :column, :block].each do |type|
22
+ search_pair(type, index)
23
+ search_single(type, index)
24
+ only_one(type, index)
25
+ end
26
+ end
27
+ end
28
+ self
29
+ end
30
+
31
+ def create_revers(data)
32
+ @revers, index = raw_revers, 0
33
+ @revers.each_with_index do |row, row_index|
34
+ row.each_with_index do |col, col_index|
35
+ cell = data[index]
36
+ @revers[row_index][col_index] = [cell.to_i] if cell =~ /^[1-9]$/
37
+ index += 1
38
+ end
39
+ end
40
+ end
41
+
42
+ # revers table @revers into string where cell [n] return n and [a1, a2, ..., ak] return 0
43
+ def sudoku
44
+ result = ""
45
+ @revers.flatten(1).each do |element|
46
+ result << (element.size == 1 ? element.first.to_s : "0")
47
+ end
48
+ result
49
+ end
50
+
51
+ # change positions: external position of block for sudoku and
52
+ # internal position of block into array [row_position, column_position]
53
+ def block_position(external, internal)
54
+ [
55
+ (external / @block_width) * @block_width + internal / @block_width,
56
+ (external % @block_width) * @block_width + internal % @block_width
57
+ ]
58
+ end
59
+
60
+ # get vector values for row, column or block from variable @revers
61
+ def get_vector(type, index)
62
+ result = []
63
+ case type
64
+ when :row
65
+ result = @revers[index]
66
+ when :column
67
+ result = @revers.map{ |element| element[index] }
68
+ when :block
69
+ @width.times do |block|
70
+ row, column = block_position(index, block)
71
+ result << @revers[row][column]
72
+ end
73
+ end
74
+ result
75
+ end
76
+
77
+ # get vector values for row, column or block from variable @revers
78
+ def set_vector(type, index, vector)
79
+ case type
80
+ when :row
81
+ @revers[index] = vector
82
+ when :column
83
+ @width.times do |row|
84
+ @revers[row][index] = vector[row]
85
+ end
86
+ when :block
87
+ @width.times do |block|
88
+ row, column = block_position(index, block)
89
+ @revers[row][column] = vector[block]
90
+ end
91
+ end
92
+ end
93
+
94
+ def positions_values(vector)
95
+ result = {}
96
+ vector.each_with_index do |values, index|
97
+ values.each do |value|
98
+ (result[value] ||= []) << index
99
+ end
100
+ end
101
+ result
102
+ end
103
+
104
+ def only_two_positions(vector)
105
+ result = {}
106
+ positions_values(vector).select do |value, positions|
107
+ positions.length == 2
108
+ end.each do |value, positions|
109
+ (result[positions] ||= []) << value
110
+ end
111
+ result
112
+ end
113
+
114
+ # if vector (row, column or block) has got two cells like this [n1, n2, n3] and [n1, n2, n4]
115
+ # and variables n1, n2 don't exist in other cells of vector than two cells should be equal [n1, n2].
116
+ # [[1, 4], [2, 4, 1, 3], [1, 2, 4], [4]] #=> [[1, 4], [2, 4], [2, 4], [4]]
117
+ def search_pair(type, index)
118
+ vec = get_vector(type, index)
119
+ only_two_positions(vec).each do |positions, value|
120
+ next unless value.size == 2
121
+ vec[positions[0]] = value.clone
122
+ vec[positions[1]] = value.clone
123
+ end
124
+ set_vector(type, index, vec)
125
+ end
126
+
127
+ # if vector (row, column or block) has got cell like this [n1, n2, ..., nk]
128
+ # and variable ni exist only one in all vector than cell should be equal [ni].
129
+ # [[1, 2, 3, 4], [2, 4], [1, 2, 4], [1, 4]] #=> [[3], [2, 4], [1, 2, 4], [1, 4]]
130
+ def only_one(type, index)
131
+ vec = get_vector(type, index)
132
+ vec.each_with_index do |values, position|
133
+ next unless values.size == 1
134
+ cell = values.first
135
+ @width.times do |sub_position|
136
+ vec[sub_position].delete(cell) unless sub_position == position
137
+ end
138
+ end
139
+ set_vector(type, index, vec)
140
+ end
141
+
142
+ # if vector (row, column or block) has got cell like this [n1]
143
+ # than should be erase value n1 from rest of cells.
144
+ # [[1, 2, 3, 4], [2], [1, 2, 4], [1, 4]] #=> [[1, 3, 4], [2], [1, 4], [1, 4]]
145
+ def search_single(type, index)
146
+ vec = get_vector(type, index)
147
+ positions_values(vec).each do |value, positions|
148
+ vec[positions.first] = [value] if positions.size == 1
149
+ end
150
+ set_vector(type, index, vec)
151
+ end
152
+
153
+ # @width = 4
154
+ # cell = [1, 2, 3, 4]
155
+ # return [[cell, cell, cell, cell],
156
+ # [cell, cell, cell, cell],
157
+ # [cell, cell, cell, cell],
158
+ # [cell, cell, cell, cell]]
159
+ def raw_revers
160
+ (1..@width).to_a.map do
161
+ (1..@width).to_a.map do
162
+ (1..@width).to_a
163
+ end
164
+ end
165
+ end
166
+
167
+ end
168
+ end
@@ -1,3 +1,3 @@
1
1
  class NSudoku
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/nsudoku.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  # encoding: utf-8
2
2
  require_relative "nsudoku/version"
3
3
  require_relative "nsudoku/checker"
4
- require_relative "nsudoku/logic"
4
+ require_relative "nsudoku/solver"
5
5
 
6
6
  class NSudoku
7
7
 
8
8
  def initialize(sudoku)
9
- @data = sudoku
10
9
  @sudoku = sudoku
11
- @revers_table = create_revers_table(sudoku.split(""))
12
10
  @length = @sudoku.length
13
11
  @width = Math.sqrt(@sudoku.length).to_i
14
12
  @block_width = Math.sqrt(@width).to_i
@@ -24,27 +22,7 @@ class NSudoku
24
22
 
25
23
  # return result which match to all solutions of sudoku
26
24
  def solve
27
- revers_table = nil
28
- while not revers_table == @revers_table
29
- revers_table = Marshal.load(Marshal.dump(@revers_table))
30
-
31
- 9.times do |index|
32
- cells_with_two_elements_for('row', index)
33
- cells_with_two_elements_for('column', index)
34
- cells_with_two_elements_for('block', index)
35
- erase_in_horizontal_three(index)
36
- erase_in_vertical_three(index)
37
- end
38
-
39
- 9.times do |row|
40
- 9.times do |column|
41
- erase_in_vertical_one(row, column)
42
- erase_in_horizontal_one(row, column)
43
- erase_in_block_one(row, column)
44
- end
45
- end
46
- end
47
- revers_table_to_result
25
+ NSudoku::Solver.new(@sudoku).search.sudoku
48
26
  end
49
27
 
50
28
  private
@@ -57,167 +35,14 @@ private
57
35
  data[position] = value.to_s
58
36
  return unless NSudoku::Checker.new(data).correct?
59
37
  @solution = data.clone if position == @length - 1
60
- if @data[position + 1] == "0"
38
+ if @sudoku[position + 1] == "0"
61
39
  (1..@width).each do |value|
62
40
  solver(data, position + 1, value)
63
41
  data[position + 1] = "0"
64
42
  end
65
43
  else
66
- solver(data, position + 1, @data[position + 1])
67
- end
68
- end
69
-
70
- # table = [[[1, 2, 3, 4, 5], [], ..., []],
71
- # [[], ..., []],
72
- # ...
73
- # [[], ..., []]
74
- # ]
75
- def create_revers_table(data)
76
- result = 9.times.map{|row_element| 9.times.map{|col_element| []}}
77
- data.each_with_index do |cell, index|
78
- column = index % 9
79
- row = (index - column)/9 % 9
80
- cell = cell.to_i
81
- result[row][column] = [cell]
82
- result[row][column] = 9.times.map{|element| element + 1} if cell == 0
83
- end
84
- return result
85
- end
86
-
87
- # revers table @revers_table into string where cell [n] return n and [a1, a2, ..., ak] return 0
88
- def revers_table_to_result
89
- result = ""
90
- @revers_table.flatten(1).each do |element|
91
- result << (element.size == 1 ? element.first.to_s : "0")
92
- end
93
- result
94
- end
95
-
96
-
97
- def positions_for(type, index0, index1)
98
- case type
99
- when 'column'
100
- return {
101
- row: index0,
102
- column: index1
103
- }
104
- when 'row'
105
- return {
106
- row: index0,
107
- column: index1
108
- }
109
- when 'block'
110
- return {
111
- row: (index0/3)*3 + index1/3,
112
- column: (index0%3)*3 + index1%3
113
- }
114
- end
115
- end
116
-
117
- def positions_of_values(kind, value)
118
- result = {}
119
- row, column = nil, nil
120
- 9.times do |index|
121
- case kind
122
- when 'column'
123
- row = index
124
- column = value
125
- when 'row'
126
- row = value
127
- column = index
128
- when 'block'
129
- row = (value/3)*3 + index/3
130
- column = (value%3)*3 + index%3
131
- end
132
- next if row.nil? or column.nil?
133
- @revers_table[row][column].each do |value|
134
- result[value] ||= []
135
- result[value] << index
136
- end
137
- end
138
- result
139
- end
140
-
141
- # erase values in vertical, which are that same like value from positnio row, column
142
- # type: "block", "row","column"
143
- def cells_with_two_elements_for(type, index)
144
- only_two_positions = positions_of_values(type, index).select {|k,v| v.length == 2}
145
- result = {}
146
- only_two_positions.each do |value, positions|
147
- result[positions] ||= []
148
- result[positions] << value
44
+ solver(data, position + 1, @sudoku[position + 1])
149
45
  end
150
- result.each do |key, value|
151
- if value.size == 2
152
- if type == 'block'
153
- positions = positions_for('block', index, key[0])
154
- @revers_table[positions[:row]][positions[:column]] = value.clone
155
- positions = positions_for('block', index, key[1])
156
- @revers_table[positions[:row]][positions[:column]] = value.clone
157
- end
158
- if type == 'row'
159
- @revers_table[index][key[0]] = value.clone
160
- @revers_table[index][key[1]] = value.clone
161
- end
162
- if type == 'column'
163
- @revers_table[key[0]][index] = value.clone
164
- @revers_table[key[1]][index] = value.clone
165
- end
166
- end
167
- end
168
- end
169
-
170
-
171
- # erase values in vertical, which are that same like value from positnio row, column
172
- def erase_in_vertical_one(row, column)
173
- return unless @revers_table[row][column].size == 1
174
- cell = @revers_table[row][column][0]
175
- 9.times do |index|
176
- @revers_table[index][column].delete(cell) unless index == row
177
- end
178
- end
179
-
180
- # erase values in hozizontal, which are that same like value from positnio row, column
181
- def erase_in_horizontal_one(row, column)
182
- return unless @revers_table[row][column].size == 1
183
- cell = @revers_table[row][column][0]
184
- 9.times do |index|
185
- @revers_table[row][index].delete(cell) unless index == column
186
- end
187
- end
188
-
189
- # erase values in block, which are that same like value from positnio row, column
190
- def erase_in_block_one(row, column)
191
- return unless @revers_table[row][column].size == 1
192
- cell = @revers_table[row][column][0]
193
- first_row_block = (row/3)*3
194
- first_column_block = (column/3)*3
195
- 3.times do |row_index|
196
- row_block = first_row_block + row_index
197
- 3.times do |column_index|
198
- column_block = first_column_block + column_index
199
- @revers_table[row_block][column_block].delete(cell) unless row_block == row and column_block == column
200
- end
201
- end
202
- end
203
-
204
- # erase values in vertical, which are that same like value from positnio row, column
205
- def erase_in_vertical_three(column)
206
- positions_of_values('column', column).each do |value, positions|
207
- @revers_table[positions.first][column] = [value] if positions.size == 1
208
- end
209
- end
210
-
211
- # erase values in horizontal, which are that same like value from positnio row, column
212
- def erase_in_horizontal_three(row)
213
- positions_of_values('row', row).each do |value, positions|
214
- @revers_table[row][positions.first] = [value] if positions.size == 1
215
- end
216
- end
217
-
218
- def cell_revers_table(row, column, value = nil)
219
- @revers_table[row][column] = value unless value.nil?
220
- @revers_table[row][column]
221
46
  end
222
47
 
223
48
  end
data/nsudoku.gemspec CHANGED
@@ -5,8 +5,8 @@ Gem::Specification.new do |gem|
5
5
  gem.platform = Gem::Platform::RUBY
6
6
  gem.authors = ["Michał Szyma"]
7
7
  gem.email = ["raglub.ruby@gmail.com"]
8
- gem.date = "2012-11-13"
9
- gem.description = %q{This gem solve puzzle game sudoku 9x9}
8
+ gem.date = "2012-11-16"
9
+ gem.description = %q{This gem solve puzzle game sudoku (n^2, n^2), for example (3^2, 3^2) = (9, 9)}
10
10
  gem.summary = %q{This gem solve puzzle game sudoku}
11
11
  gem.homepage = "http://github.com/raglub/nsudoku"
12
12
 
data/spec/nsudoku_spec.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'nsudoku'
4
4
 
5
- EXAMPLE = [
5
+ SUDOKU99 = [
6
6
  "012000000",
7
7
  "304052000",
8
8
  "605170000",
@@ -13,17 +13,6 @@ EXAMPLE = [
13
13
  "000580107",
14
14
  "000000240"].join
15
15
 
16
- TABLE_EMPTY = [
17
- "000000000",
18
- "000000000",
19
- "000000000",
20
- "000000000",
21
- "000000000",
22
- "000000000",
23
- "000000000",
24
- "000000000",
25
- "000000000"].join
26
-
27
16
  SUDOKU44 = [
28
17
  "1030",
29
18
  "4000",
@@ -34,125 +23,11 @@ describe NSudoku do
34
23
 
35
24
  it "should properly resolved game sudoku" do
36
25
  NSudoku.new(SUDOKU44).solution.should eq("1234431221433421")
26
+ NSudoku.new(SUDOKU99).solution.should eq("712463859394852671685179423173248596926715384458936712541327968269584137837691245")
37
27
  end
38
28
 
39
- it "should properly reverse base namber of table" do
40
- sudoku = NSudoku.new(TABLE_EMPTY)
41
- sudoku.send(:cell_revers_table, 1, 1, [1])
42
- sudoku.send(:cell_revers_table, 8, 8, [3])
43
- sudoku.send(:cell_revers_table, 1, 1).should eql([1])
44
- sudoku.send(:cell_revers_table, 1 ,0).should eql([1, 2, 3, 4, 5, 6, 7, 8, 9])
45
- end
46
-
47
- context "should properly erase values which are that same like in cell with single element" do
48
- it "in column (exist cell with single element)" do
49
- sudoku = NSudoku.new(TABLE_EMPTY)
50
- sudoku.send(:cell_revers_table, 3, 4, [5])
51
- sudoku.send(:erase_in_vertical_one, 3, 4)
52
- sudoku.send(:cell_revers_table, 0, 4).should eql([1, 2, 3, 4, 6, 7, 8, 9])
53
- sudoku.send(:cell_revers_table, 5, 4).should eql([1, 2, 3, 4, 6, 7, 8, 9])
54
- sudoku.send(:cell_revers_table, 8, 4).should eql([1, 2, 3, 4, 6, 7, 8, 9])
55
- sudoku.send(:cell_revers_table, 3, 4).should eql([5])
56
- end
57
-
58
- it "in column (don't exist cell with single element)" do
59
- sudoku = NSudoku.new(TABLE_EMPTY)
60
- sudoku.send(:erase_in_vertical_one, 3, 4)
61
- sudoku.send(:cell_revers_table, 5, 4).should eql([1, 2, 3, 4,5 , 6, 7, 8, 9])
62
- sudoku.send(:cell_revers_table, 3, 4).should eql([1, 2, 3, 4,5 , 6, 7, 8, 9])
63
- end
64
-
65
- it "in row (exist cell with single element)" do
66
- sudoku = NSudoku.new(TABLE_EMPTY)
67
- sudoku.send(:cell_revers_table, 3, 4, [5])
68
- sudoku.send(:erase_in_horizontal_one, 3, 4)
69
- sudoku.send(:cell_revers_table, 3, 2).should eql([1, 2, 3, 4, 6, 7, 8, 9])
70
- sudoku.send(:cell_revers_table, 3, 4).should eql([5])
71
- end
72
-
73
- it "in column (don't exist cell with single element)" do
74
- sudoku = NSudoku.new(TABLE_EMPTY)
75
- sudoku.send(:erase_in_horizontal_one, 3, 4)
76
- sudoku.send(:cell_revers_table, 3, 2).should eql([1, 2, 3, 4, 5, 6, 7, 8, 9])
77
- sudoku.send(:cell_revers_table, 3, 4).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
78
- end
79
-
80
- it "in block (exist cell with single element)" do
81
- sudoku = NSudoku.new(TABLE_EMPTY)
82
- sudoku.send(:cell_revers_table, 7, 3, [8])
83
- sudoku.send(:erase_in_block_one, 7, 3)
84
- sudoku.send(:cell_revers_table, 6, 2).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
85
- sudoku.send(:cell_revers_table, 5, 6).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
86
- sudoku.send(:cell_revers_table, 5, 3).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
87
- sudoku.send(:cell_revers_table, 7, 6).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
88
-
89
- sudoku.send(:cell_revers_table, 6, 4).should eql([1, 2, 3, 4 ,5, 6, 7, 9])
90
- sudoku.send(:cell_revers_table, 8, 3).should eql([1, 2, 3, 4 ,5, 6, 7, 9])
91
- sudoku.send(:cell_revers_table, 8, 5).should eql([1, 2, 3, 4 ,5, 6, 7, 9])
92
- sudoku.send(:cell_revers_table, 7, 3).should eql([8])
93
- end
94
-
95
- it "in block (don't exist cell with single element)" do
96
- sudoku = NSudoku.new(TABLE_EMPTY)
97
- sudoku.send(:erase_in_block_one, 7, 3)
98
- sudoku.send(:cell_revers_table, 6, 2).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
99
- sudoku.send(:cell_revers_table, 5, 6).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
100
-
101
- sudoku.send(:cell_revers_table, 6, 4).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
102
- sudoku.send(:cell_revers_table, 8, 3).should eql([1, 2, 3, 4 ,5, 6, 7, 8, 9])
103
- end
104
- end
105
-
106
- context "select cells with two elements (rest erase)" do
107
-
108
- it "in block" do
109
- sudoku = NSudoku.new(TABLE_EMPTY)
110
- 3.times do |row|
111
- 3.times do |column|
112
- sudoku.send(:cell_revers_table, row, column, [2, 3, 4, 5, 6, 7, 9])
113
- end
114
- end
115
-
116
- sudoku.send(:cell_revers_table, 0, 0, [1, 5, 8])
117
- sudoku.send(:cell_revers_table, 2, 2, [1, 3, 8])
118
-
119
- sudoku.send(:cells_with_two_elements_for, "block", 0)
120
- sudoku.send(:cell_revers_table, 0, 0).should eql([1, 8])
121
- sudoku.send(:cell_revers_table, 2, 2).should eql([1, 8])
122
- sudoku.send(:cell_revers_table, 1, 2).should eql([2, 3, 4, 5, 6, 7, 9])
123
- end
124
-
125
- it "in column" do
126
- sudoku = NSudoku.new(TABLE_EMPTY)
127
- sudoku.send(:cell_revers_table, 0, 4, [1, 5, 8])
128
- sudoku.send(:cell_revers_table, 1, 4, [1, 3, 8])
129
- (2..8).each do |index|
130
- sudoku.send(:cell_revers_table, index, 4, [2, 3, 4, 5, 6, 7, 9])
131
- end
132
-
133
- sudoku.send(:cells_with_two_elements_for, "column", 4)
134
- sudoku.send(:cell_revers_table, 0, 4).should eql([1, 8])
135
- sudoku.send(:cell_revers_table, 1, 4).should eql([1, 8])
136
- sudoku.send(:cell_revers_table, 2, 4).should eql([2, 3, 4, 5, 6, 7, 9])
137
- end
138
-
139
- it "in row" do
140
- sudoku = NSudoku.new(TABLE_EMPTY)
141
- sudoku.send(:cell_revers_table, 4, 0, [1, 5, 8])
142
- sudoku.send(:cell_revers_table, 4, 1, [1, 3, 8])
143
- (2..8).each do |index|
144
- sudoku.send(:cell_revers_table, 4, index, [2, 3, 4, 5, 6, 7, 9])
145
- end
146
-
147
- sudoku.send(:cells_with_two_elements_for, "row", 4)
148
- sudoku.send(:cell_revers_table, 4, 0).should eql([1, 8])
149
- sudoku.send(:cell_revers_table, 4, 1).should eql([1, 8])
150
- sudoku.send(:cell_revers_table, 4, 2).should eql([2, 3, 4, 5, 6, 7, 9])
151
- end
152
-
153
- end
154
-
155
- it "solve whole sudoku" do
156
- NSudoku.new(EXAMPLE).solve.should eql("712460850394852671685170420070208596926715384050906712540320968269584137030690245")
29
+ it "should solve game sudoku" do
30
+ NSudoku.new(SUDOKU44).solve.should eq("1234431221433421")
31
+ NSudoku.new(SUDOKU99).solve.should eql("712460850394852671685170420070208596926715384050906712540320968269584137030690245")
157
32
  end
158
33
  end
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+
3
+ require 'nsudoku'
4
+
5
+ EXAMPLE = [
6
+ "1004",
7
+ "0302",
8
+ "0143",
9
+ "3020"].join
10
+
11
+ # REVERS = [
12
+ # [[1], [1, 2, 3, 4], [1, 2, 3, 4], [4]],
13
+ # [[1, 2, 3, 4], [2], [1, 2, 3, 4], [1]],
14
+ # [[1, 2, 3, 4], [1], [4], [3]],
15
+ # [[2], [1, 2, 3, 4], [1], [1, 2, 3, 4]]
16
+ # ]
17
+
18
+ describe NSudoku do
19
+
20
+ before do
21
+ @logic = NSudoku::Solver.new(EXAMPLE)
22
+ end
23
+
24
+ it "can generate reverse of sudoku" do
25
+ @logic.revers[0][1].should eql([1, 2, 3, 4])
26
+ @logic.revers[3][3].inspect
27
+ end
28
+
29
+ it "can get result" do
30
+ @logic.sudoku.should eql(EXAMPLE)
31
+ end
32
+
33
+ context "can erase that same elements in" do
34
+ it "column" do
35
+ @logic.set_vector(:column, 3, [[1, 3], [1, 2, 3, 4], [1, 3, 4], [4]])
36
+ @logic.only_one(:column, 3)
37
+ @logic.get_vector(:column, 3).should eq([[1, 3], [1, 2, 3], [1, 3], [4]])
38
+ end
39
+
40
+ it "row" do
41
+ @logic.set_vector(:column, 3, [[1, 3], [1, 2, 3, 4], [1, 3, 4], [4]])
42
+ @logic.only_one(:column, 3)
43
+ @logic.get_vector(:column, 3).should eq([[1, 3], [1, 2, 3], [1, 3], [4]])
44
+ end
45
+
46
+ it "block" do
47
+ @logic.set_vector(:block, 1, [[1, 2, 3], [2], [1, 2, 3, 4], [4]])
48
+ @logic.only_one(:block, 1)
49
+ @logic.get_vector(:block, 1).should eq([[1, 3], [2], [1, 3], [4]])
50
+ end
51
+ end
52
+
53
+ context "can choos only one value in elements for" do
54
+ it "vertical situation" do
55
+ @logic.set_vector(:column, 2, [[1, 3], [1, 2, 3, 4], [1, 3, 4], [4]])
56
+ @logic.search_single(:column, 2)
57
+ @logic.get_vector(:column, 2).should eq([[1, 3], [2], [1, 3, 4], [4]])
58
+ end
59
+
60
+ it "horizontal situation" do
61
+ @logic.set_vector(:row, 2, [[1, 3], [1, 2, 3, 4], [1, 3, 4], [4]])
62
+ @logic.search_single(:row, 2)
63
+ @logic.get_vector(:row, 2).should eq([[1, 3], [2], [1, 3, 4], [4]])
64
+ end
65
+
66
+ it "block situation" do
67
+ @logic.set_vector(:block, 2, [[1, 3], [1, 2, 3, 4], [1, 3, 4], [4]])
68
+ @logic.search_single(:block, 2)
69
+ @logic.get_vector(:block, 2).should eq([[1, 3], [2], [1, 3, 4], [4]])
70
+ end
71
+ end
72
+
73
+ context "can choose only two values in elements for" do
74
+ it "vertical situation" do
75
+ @logic.set_vector(:column, 2, [[1], [1, 2, 3, 4], [1, 2, 3, 4], [4]])
76
+ @logic.search_pair(:column, 2)
77
+ @logic.get_vector(:column, 2).should eq([[1], [2, 3], [2, 3], [4]])
78
+ end
79
+
80
+ it "horizontal situation" do
81
+ @logic.set_vector(:row, 3, [[3], [1, 2, 3, 4], [1, 2, 4], [4]])
82
+ @logic.search_pair(:row, 3)
83
+ @logic.get_vector(:row, 3).should eq([[3], [1, 2], [1, 2], [4]])
84
+ end
85
+
86
+ it "block situation" do
87
+ @logic.set_vector(:block, 0, [[1], [1, 2, 3, 4], [1, 2, 3, 4], [4]])
88
+ @logic.search_pair(:block, 0)
89
+ @logic.get_vector(:block, 0).should eq([[1], [2, 3], [2, 3], [4]])
90
+ end
91
+ end
92
+
93
+ it "can solve the sudoku" do
94
+ NSudoku::Solver.new(EXAMPLE).search.sudoku.should eql(
95
+ "1234" +
96
+ "4312" +
97
+ "2143" +
98
+ "3421"
99
+ )
100
+ end
101
+
102
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nsudoku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-13 00:00:00.000000000 Z
12
+ date: 2012-11-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -27,7 +27,8 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: 2.10.0
30
- description: This gem solve puzzle game sudoku 9x9
30
+ description: This gem solve puzzle game sudoku (n^2, n^2), for example (3^2, 3^2)
31
+ = (9, 9)
31
32
  email:
32
33
  - raglub.ruby@gmail.com
33
34
  executables: []
@@ -42,13 +43,13 @@ files:
42
43
  - Rakefile
43
44
  - lib/nsudoku.rb
44
45
  - lib/nsudoku/checker.rb
45
- - lib/nsudoku/logic.rb
46
+ - lib/nsudoku/solver.rb
46
47
  - lib/nsudoku/version.rb
47
48
  - lib/sudoku.rb~
48
49
  - nsudoku.gemspec
49
50
  - spec/checker_spec.rb
50
- - spec/logic_spec.rb
51
51
  - spec/nsudoku_spec.rb
52
+ - spec/solver_spec.rb
52
53
  homepage: http://github.com/raglub/nsudoku
53
54
  licenses: []
54
55
  post_install_message:
@@ -75,5 +76,5 @@ specification_version: 3
75
76
  summary: This gem solve puzzle game sudoku
76
77
  test_files:
77
78
  - spec/checker_spec.rb
78
- - spec/logic_spec.rb
79
79
  - spec/nsudoku_spec.rb
80
+ - spec/solver_spec.rb
data/lib/nsudoku/logic.rb DELETED
@@ -1,187 +0,0 @@
1
- # encoding: utf-8
2
-
3
- class NSudoku
4
- class Logic
5
-
6
- attr_accessor :revers
7
-
8
- def initialize(sudoku)
9
- @sudoku = sudoku
10
- @width = Math.sqrt(sudoku.length).to_i
11
- @block_width = Math.sqrt(@width).to_i
12
- @revers = create_revers(@sudoku.split(""))
13
- end
14
-
15
- def create_revers(data)
16
- @revers, index = raw_revers, 0
17
- @revers.each_with_index do |row, row_index|
18
- row.each_with_index do |col, col_index|
19
- cell = data[index]
20
- @revers[row_index][col_index] = [cell.to_i] if cell =~ /^[1-9]$/
21
- index += 1
22
- end
23
- end
24
- end
25
-
26
- # revers table @revers_table into string where cell [n] return n and [a1, a2, ..., ak] return 0
27
- def sudoku
28
- result = ""
29
- @revers.flatten(1).each do |element|
30
- result << (element.size == 1 ? element.first.to_s : "0")
31
- end
32
- result
33
- end
34
-
35
- # change positions: external position of block for sudoku and
36
- # internal position of block into array [row_position, column_position]
37
- def block_position(external, internal)
38
- [
39
- (external / @block_width) * @block_width + internal / @block_width,
40
- (external % @block_width) * @block_width + internal % @block_width
41
- ]
42
- end
43
-
44
- def block_positions_values(external)
45
- result = {}
46
- @width.times do |internal|
47
- row, column = block_position(external, internal)
48
- @revers[row][column].each { |value| (result[value] ||= []) << [row, column] }
49
- end
50
- result
51
- end
52
-
53
- def row_positions_values(row)
54
- result = {}
55
- @width.times do |column|
56
- @revers[row][column].each { |value| (result[value] ||= []) << column }
57
- end
58
- result
59
- end
60
-
61
- def column_positions_values(column)
62
- result = {}
63
- @width.times do |row|
64
- @revers[row][column].each { |value| (result[value] ||= []) << row }
65
- end
66
- result
67
- end
68
-
69
- # erase values in vertical, which are that same like value from positnio row, column
70
- def one_element_vertical(column)
71
- column_positions_values(column).each do |value, positions|
72
- @revers[positions.first][column] = [value] if positions.size == 1
73
- end
74
- self
75
- end
76
-
77
- # erase values in horizontal, which are that same like value from positnio row, column
78
- def one_element_horizontal(row)
79
- row_positions_values(row).each do |value, positions|
80
- @revers[row][positions.first] = [value] if positions.size == 1
81
- end
82
- self
83
- end
84
-
85
- # erase values in block, which are that same like value from positnio row, column
86
- def one_element_block(row)
87
- block_positions_values(row).each do |value, positions|
88
- horizontal = positions[0][0]
89
- vertical = positions[0][1]
90
- @revers[horizontal][vertical] = [value] if positions.size == 1
91
- end
92
- self
93
- end
94
-
95
- # erase values in block, which are that same like value from positnio row, column
96
- def two_elements_block(index)
97
- result = {}
98
- only_two_positions = block_positions_values(index).select {|k,v| v.length == 2}
99
- only_two_positions.each { |value, positions| (result[positions] ||= []) << value }
100
- result.each do |positions, value|
101
- next unless value.size == 2
102
- @revers[positions[0][0]][positions[0][1]] = value.clone
103
- @revers[positions[1][0]][positions[1][1]] = value.clone
104
- end
105
- self
106
- end
107
-
108
- # erase values in vertical, which are that same like value from positnio row, column
109
- def two_elements_column(column)
110
- result = {}
111
- only_two_positions = row_positions_values(column).select {|k,v| v.length == 2}
112
- only_two_positions.each { |value, positions| (result[positions] ||= []) << value }
113
- result.each do |positions, value|
114
- next unless value.size == 2
115
- @revers[positions[0]][column] = value.clone
116
- @revers[positions[1]][column] = value.clone
117
- end
118
- self
119
- end
120
-
121
- # erase values in row, which are that same like value from positnio row, column
122
- def two_elements_row(row)
123
- result = {}
124
- only_two_positions = column_positions_values(row).select {|k,v| v.length == 2}
125
- only_two_positions.each { |value, positions| (result[positions] ||= []) << value }
126
- result.each do |positions, value|
127
- next unless value.size == 2
128
- @revers[row][positions[0]] = value.clone
129
- @revers[row][positions[1]] = value.clone
130
- end
131
- self
132
- end
133
-
134
- # erase values in vertical, which are that same like value from positnio row, column
135
- def erase_elements_row(row, column)
136
- return unless @revers[row][column].size == 1
137
- cell = @revers[row][column][0]
138
- @width.times do |index|
139
- @revers[index][column].delete(cell) unless index == row
140
- end
141
- self
142
- end
143
-
144
- # erase values in hozizontal, which are that same like value from positnio row, column
145
- def erase_elements_column(row, column)
146
- return unless @revers[row][column].size == 1
147
- cell = @revers[row][column][0]
148
- @width.times do |index|
149
- @revers[row][index].delete(cell) unless index == column
150
- end
151
- self
152
- end
153
-
154
- # erase values in block, which are that same like value from positnio row, column
155
- def erase_elements_block(row, column)
156
- return unless @revers[row][column].size == 1
157
- cell = @revers[row][column][0]
158
- block_positions(row, column).each do |horizontal, vertical|
159
- @revers[horizontal][vertical].delete(cell) unless horizontal == row and vertical == column
160
- end
161
- self
162
- end
163
-
164
- def block_positions(row, column)
165
- row_block = (row / @block_width) * @block_width
166
- column_block = (column / @block_width) * @block_width
167
- (0...@width).to_a.map { |index| [row_block + index / @block_width, column_block + index % @block_width] }
168
- end
169
-
170
- private
171
-
172
- # @width = 4
173
- # cell = [1, 2, 3, 4]
174
- # return [[cell, cell, cell, cell],
175
- # [cell, cell, cell, cell],
176
- # [cell, cell, cell, cell],
177
- # [cell, cell, cell, cell]]
178
- def raw_revers
179
- (1..@width).to_a.map do
180
- (1..@width).to_a.map do
181
- (1..@width).to_a
182
- end
183
- end
184
- end
185
-
186
- end
187
- end
data/spec/logic_spec.rb DELETED
@@ -1,85 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'nsudoku'
4
-
5
- EXAMPLE = [
6
- "1004",
7
- "0201",
8
- "0143",
9
- "2010"].join
10
-
11
- # REVERS = [
12
- # [[1], [1, 2, 3, 4], [1, 2, 3, 4], [4]],
13
- # [[1, 2, 3, 4], [2], [1, 2, 3, 4], [1]],
14
- # [[1, 2, 3, 4], [1], [4], [3]],
15
- # [[2], [1, 2, 3, 4], [1], [1, 2, 3, 4]]
16
- # ]
17
-
18
- REVERS = [
19
- [[3], [1, 2, 3, 4], [1, 2, 4], [4]],
20
- [[1, 2, 4], [2], [1, 2, 3, 4], [1]],
21
- [[1, 2, 3], [1, 2, 4], [4], [3]],
22
- [[4], [3, 4], [1], [1, 2, 3, 4]]
23
- ]
24
-
25
- describe NSudoku do
26
-
27
- before do
28
- @logic = NSudoku::Logic.new(EXAMPLE)
29
- end
30
-
31
- it "can generate reverse of sudoku" do
32
- @logic.revers[0][1].should eql([1, 2, 3, 4])
33
- @logic.revers[3][3].inspect
34
- end
35
-
36
- it "can get result" do
37
- @logic.sudoku.should eql(EXAMPLE)
38
- end
39
-
40
- it "can erase that same elements in column" do
41
- @logic.erase_elements_column(0, 0).revers[0][1].should eql([2, 3, 4])
42
- end
43
-
44
- it "can erase that same elements in row" do
45
- @logic.erase_elements_row(2, 2).revers[1][2].should eql([1, 2, 3])
46
- end
47
-
48
- it "can erase that same elements in block" do
49
- @logic.erase_elements_block(2, 2).revers[3][3].should eql([1, 2, 3])
50
- end
51
-
52
- it "can choos only one value in elements for vertical situation" do
53
- @logic.revers = REVERS
54
- @logic.one_element_vertical(2).revers[1][2].should eql([3])
55
- end
56
-
57
- it "can choos only one value in elements for horizontal situation" do
58
- @logic.revers = REVERS
59
- @logic.one_element_horizontal(2).revers[1][2].should eql([3])
60
- end
61
-
62
- it "can choos only one value in elements for block situation" do
63
- @logic.revers = REVERS
64
- @logic.one_element_block(2).revers[1][2].should eql([3])
65
- end
66
-
67
- it "can choos only two values in elements for block situation" do
68
- @logic.revers = REVERS
69
- @logic.two_elements_block(2).revers[2][0].should eql([1, 2])
70
- @logic.revers[2][1].should eql([1, 2])
71
- end
72
-
73
- it "can choos only two values in elements for vertical situation" do
74
- @logic.revers = REVERS
75
- @logic.two_elements_column(0).revers[1][0].should eql([1, 2])
76
- @logic.revers[2][0].should eql([1, 2])
77
- end
78
-
79
- it "can choos only two values in elements for horizontal situation" do
80
- @logic.revers = REVERS
81
- @logic.two_elements_row(2).revers[2][0].should eql([1, 2])
82
- @logic.revers[2][1].should eql([1, 2])
83
- end
84
-
85
- end