sudokuhandler 0.1.2 → 0.1.3

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,176 @@
1
+ module Sudoku
2
+ #Methodes de resolution des sudokus
3
+ module Solver
4
+ #Renvoie les nombres manquants dans le carré comprenant la case x,y
5
+ # @param [Fixnum] x La colonne d'une case du carré à traiter
6
+ # @param [Fixnum] y La rangée d'une case du carré à traiter
7
+ # @return [Array] Les nombres manquants
8
+ def missing_square x, y
9
+ Array.new(size){|i| i+1} - square(x,y)
10
+ end
11
+
12
+ #Renvoie les nombres manquants dans la colonne
13
+ # @param [Fixnum] x La colonne à traiter
14
+ # @return [Array] Les nombres manquants
15
+ def missing_col x
16
+ Array.new(size){|i| i+1} - col(x)
17
+ end
18
+
19
+ #Renvoie les nombres manquants dans la colonne
20
+ # @param [Fixnum] y La ligne à traiter
21
+ # @return [Array] Les nombres manquants
22
+ def missing_row y
23
+ Array.new(size){|i| i+1} - row(y)
24
+ end
25
+
26
+ #Ajoute un nombre chaque fois que c'est la seule possibilité
27
+ # @return [Fixnum] le nombre de nombres ajoutés dans la grille
28
+ def solve_uniq_possibilities!
29
+ res = 0
30
+ loop do
31
+ adds = 0
32
+ each do |x, y, val|
33
+ next unless val.zero?
34
+
35
+ p = possibilities x, y
36
+ if p.length == 1
37
+ set x, y, p.first
38
+ adds += 1
39
+ end
40
+
41
+ end
42
+ break if adds == 0
43
+ res += adds
44
+ end
45
+ res
46
+ end
47
+
48
+ #Ajoute un nombre chaque fois que c'est la seule position possible dans la colonne
49
+ # @param [Fixnum] x La colonne à traiter
50
+ # @return [Fixnum] le nombre de nombres ajoutés
51
+ def solve_col! x
52
+ adds = 0
53
+
54
+ missing_col(x).each do |val|
55
+ pos = []
56
+ size.times do |y|
57
+ next unless get(x,y) == 0
58
+ pos << [x,y] if valid? x, y, val
59
+ end
60
+ if pos.length == 1
61
+ set pos[0][0], pos[0][1], val
62
+ adds += 1
63
+ end
64
+ end
65
+
66
+ adds
67
+ end
68
+
69
+ #Ajoute un nombre chaque fois que c'est la seule position possible dans la ligne
70
+ # @param [Fixnum] y La ligne à traiter
71
+ # @return [Fixnum] le nombre de nombres ajoutés
72
+ def solve_row! y
73
+ adds = 0
74
+
75
+ missing_row(y).each do |val|
76
+ pos = []
77
+ size.times do |x|
78
+ next unless get(x,y) == 0
79
+ pos << [x,y] if valid? x, y, val
80
+ end
81
+ if pos.length == 1
82
+ set pos[0][0], pos[0][1], val
83
+ adds += 1
84
+ end
85
+ end
86
+
87
+ adds
88
+ end
89
+
90
+ #Ajoute un nombre chaque fois que c'est la seule position possible dans le carré
91
+ # @param [Fixnum] x La colonne d'une case du carré à traiter
92
+ # @param [Fixnum] y La rangée d'une case du carré à traiter
93
+ # @return [Fixnum] le nombre de nombres ajoutés
94
+ def solve_square! xx, yy
95
+ xmin = xx - (xx%base)
96
+ ymin = yy - (yy%base)
97
+ adds = 0
98
+
99
+ missing_square(xx, yy).each do |val|
100
+ pos = []
101
+ base.times do |i|
102
+ base.times do |j|
103
+ x = xmin + i
104
+ y = ymin + j
105
+ next unless get(x,y) == 0
106
+ pos << [x,y] if valid? x, y, val
107
+ end
108
+ end
109
+ if pos.length == 1
110
+ set pos[0][0], pos[0][1], val
111
+ adds += 1
112
+ end
113
+ end
114
+
115
+ adds
116
+ end
117
+
118
+ #Utilise solve_uniq_possibilities!, solve_col!, solve_row! et solve_square!
119
+ #tant qu'ils ajoutent des nombres
120
+ # @return [Fixnum] le nombre de nombres ajoutés
121
+ def solve_naive!
122
+ res = 0
123
+
124
+ loop do
125
+ adds = solve_uniq_possibilities!
126
+ size.times do |i|
127
+ adds += solve_col! i
128
+ adds += solve_row! i
129
+
130
+ x = (i*base) % size
131
+ y = (i/base) * base
132
+ adds += solve_square! x, y
133
+ end
134
+ break if adds.zero?
135
+ res += adds
136
+ end
137
+
138
+ res
139
+ end
140
+
141
+ #Resoud le sudoku par backtracking
142
+ # @return (Fixnum) le nombre de nombres ajoutés dans la grille
143
+ def solve_backtrack!
144
+ res = solve_naive!
145
+
146
+ each do |x, y, cur_val|
147
+ next unless cur_val.zero?
148
+ p = possibilities x, y
149
+ p.each do |val|
150
+ copy = clone
151
+ copy.set x, y, val
152
+ adds = copy.solve_backtrack!
153
+ if copy.complete?
154
+ self.import copy
155
+ return res+adds+1
156
+ end
157
+ end
158
+ end
159
+
160
+ res
161
+ end
162
+
163
+ #Enleve les nombres qui sont impossibles de la grille
164
+ # @return (Fixnum) le nombre de nombres enlevés
165
+ def remove_impossible!
166
+ removes = 0
167
+ each do |x, y, val|
168
+ unless valid_cell? x, y, val
169
+ set x, y, 0
170
+ removes += 1
171
+ end
172
+ end
173
+ removes
174
+ end
175
+ end
176
+ end
@@ -1,3 +1,3 @@
1
1
  module Sudoku
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/tests/test_sudoku.rb CHANGED
@@ -1,19 +1,33 @@
1
1
  require 'test/unit'
2
2
  require 'sudoku'
3
3
 
4
+ class MyTestCase < Test::Unit::TestCase
5
+ #Certains Rubys n'ont pas refute...
6
+ def refute what, *args
7
+ begin
8
+ return super(what, *args)
9
+ rescue NoMethodError => e
10
+ return assert(!what, args)
11
+ end
12
+ end
13
+
14
+ def test_myrefute
15
+ refute false, "Implementation refute"
16
+ end
17
+ end
18
+
4
19
  module GridTest
5
20
  def create
6
21
  klass.new base
7
22
  end
8
23
 
9
- def diagonal
24
+ def test_initialize
10
25
  s = create
11
- s.each{|x, y, val| s.set x,x,x+1 if x==y}
12
- s
26
+ s.each{|x,y,val| assert_equal 0, val, "Sudoku initialise a 0 partout"}
13
27
  end
14
28
 
15
29
  def test_diagonal
16
- s = diagonal
30
+ s = create.make_diagonal
17
31
  (s.size-1).times{|i| assert_equal 1, s.get(i+1, i+1)-s.get(i, i)}
18
32
  end
19
33
 
@@ -34,15 +48,16 @@ module GridTest
34
48
 
35
49
  def test_clone
36
50
  s1 = create
37
- s1.set 0, 0, 1
51
+ s1.each{|x,y,v| s1.set x, y, rand(s1.size)+1}
38
52
 
39
53
  s2 = s1.clone
40
-
41
- assert_equal 1, s2.get(0, 0)
54
+ s2.each do |x, y, v|
55
+ assert_equal s1.get(x,y), v, "Clonage cellule #{x},#{y}"
56
+ end
42
57
  end
43
58
 
44
59
  def test_each
45
- s = diagonal
60
+ s = create.make_diagonal
46
61
  s.size.times{|x| assert_equal x+1, s.get(x, x)}
47
62
  end
48
63
 
@@ -67,25 +82,27 @@ module GridTest
67
82
 
68
83
  def test_possibilities
69
84
  s = create
70
- s.set 0,0,1
85
+ s.set 1,0,1
71
86
  s.set 0,1,2
72
87
 
73
88
  assert s.possibilities(1, 1).include?(3)
74
- assert s.possibilities(1, 1).include?(4)
75
-
76
- assert s.valid?(1,1,3)
77
- assert s.valid?(1,1,4)
89
+ refute s.possibilities(1, 1).include?(1)
90
+ refute s.valid?(1,1,1), "Case non occupee, valeur impossible"
91
+ refute s.valid?(0,1,1), "Case deja occupee, valeur impossible"
92
+ assert s.valid?(1,1,4), "Case non occupee, valeur plausible"
93
+ assert s.valid?(1,1,0), "0 est toujours valide"
94
+ assert s.valid?(1,0,1), "Case deja occupee, valeur plausible"
78
95
 
79
- refute s.valid?(1,1,0)
80
- refute s.valid?(1,1,1)
81
- assert s.valid?(0,0,1)
96
+ s.possibilities(1, 1).each do |p|
97
+ assert s.valid?(1, 1, p), "Possibilite #{p} doit etre valide"
98
+ end
82
99
  end
83
100
 
84
101
  def test_sutxt
85
102
  sutxt = "#{base}:"
86
103
  size = base*base
87
104
  size.times do |y|
88
- size.times {|x| sutxt << " #{x+1}"}
105
+ size.times {|x| sutxt += " #{x+1}"}
89
106
  end
90
107
  sutxt += ';'
91
108
 
@@ -107,38 +124,94 @@ module GridTest
107
124
  assert s.completable?
108
125
  refute s.valid?
109
126
  end
110
- end
111
127
 
112
- module GeneratorTest
113
- def generator
114
- Class.new klass do
115
- include Sudoku::Generator
128
+ def test_count
129
+ s = create
130
+ assert_equal s.length, s.count(0)[0], "Comptage de 0 dans un sudoku vide"
131
+
132
+ s.make_diagonal
133
+ expected = {}
134
+ s.size.times{|x| expected[x+1] = 1}
135
+ assert_equal expected, s.count, "Comptage de valeurs dans un sudoku diagonal"
136
+
137
+ assert_raise(ArgumentError, "Comptage de valeur > size impossible"){s.count(s.size+1)}
138
+ end
139
+
140
+ def test_import
141
+ s = create.make_valid
142
+ s2 = Sudoku::Sn.new(s.size)
143
+ s2.each do |x,y,v|
144
+ assert_equal s.get(x,y), v, "Importation d'une grille vers grille generique, cellules egales"
116
145
  end
146
+
147
+ s3 = Sudoku::Sn.new(s.size+1)
148
+ assert_raise(Sudoku::NotCompatibleError, "Importation d'une grille vers grille de taille differente"){s3.import s}
117
149
  end
118
-
119
- def test_diagonal
120
- s = generator.new(base).make_diagonal
150
+ end
151
+
152
+ module GeneratorTest
153
+ def test_generator_diagonal
154
+ s = create.make_diagonal
121
155
  s.size.times{|x| assert_equal x+1, s.get(x, x)}
122
156
  end
123
157
 
124
- def test_valid
125
- s = generator.new(base).make_valid
158
+ def test_generator_valid
159
+ s = create.make_valid
126
160
  assert s.valid?
127
161
  assert s.completable?
128
162
  assert s.complete?
129
163
  end
130
164
  end
131
165
 
132
- class S3Test < Test::Unit::TestCase
166
+ module SolverTest
167
+ SOLVER_TIMEOUT = 10 #sec
168
+
169
+ def test_missing
170
+ s = create
171
+ s.set 0,1,1
172
+ s.set 1,0,2
173
+
174
+ assert s.missing_square(0,0).include?(3)
175
+ refute s.missing_square(0,0).include?(1)
176
+
177
+ assert s.missing_col(0).include?(2)
178
+ refute s.missing_col(0).include?(1)
179
+
180
+ assert s.missing_row(0).include?(1)
181
+ refute s.missing_row(0).include?(2)
182
+ end
183
+
184
+ def test_solve_uniq
185
+ s = create.make_valid
186
+ s.set 0,0,0
187
+ s.set 1,1,0
188
+
189
+ assert_equal 2, s.solve_uniq_possibilities!, "Solution par possibilites uniques d'un sudoku complet-2cases"
190
+ assert s.complete?
191
+ end
192
+
193
+ def test_solve_backtrack
194
+ s = create.make_valid_incomplete
195
+ t = Thread.new(s){|sudoku| sudoku.solve_backtrack!}
196
+ t.join SOLVER_TIMEOUT
197
+ assert s.complete?, "Toujours une solution en backtracking en max. #{SOLVER_TIMEOUT}s"
198
+ end
199
+ end
200
+
201
+ module SudokuTest
133
202
  include GridTest
203
+ include SolverTest
134
204
  include GeneratorTest
205
+ end
206
+
207
+ class S3Test < MyTestCase
208
+ include SudokuTest
135
209
 
136
210
  def base; 3; end
137
211
  def klass; Sudoku::S3; end
138
- def create; klass.new; end
139
212
 
140
213
  def test_square
141
- s = diagonal
214
+ s = create.make_diagonal
142
215
  9.times do |i|
143
216
  assert_equal [1+3*(i/3), 2+3*(i/3), 3+3*(i/3)], s.square(i,i)
144
217
  end
@@ -146,14 +219,28 @@ class S3Test < Test::Unit::TestCase
146
219
 
147
220
  def test_possibilities
148
221
  super
149
- s = diagonal
222
+ s = create.make_diagonal
150
223
  assert_equal [1, 4, 5, 6, 7, 8, 9], s.possibilities(0, 0).sort
151
224
  end
225
+
226
+ #Tests pour l'implementation en C de valid_cell?
227
+ def test_valid_cimpl
228
+ s = klass.new
229
+ s.set 0, 7, 1
230
+ s.set 7, 0, 2
231
+ s.set 1, 1, 3
232
+
233
+ refute s.valid?(0, 0, 1), "Colonne"
234
+ refute s.valid?(0, 0, 2), "Ligne"
235
+ refute s.valid?(0, 0, 3), "Carre"
236
+ assert s.valid?(0, 0, 4)
237
+
238
+ assert_raise(ArgumentError){s.valid?(9, 2, 3)}
239
+ end
152
240
  end
153
241
 
154
- class S4_15Test < Test::Unit::TestCase
155
- include GridTest
156
- include GeneratorTest
242
+ class S4_15Test < MyTestCase
243
+ include SudokuTest
157
244
 
158
245
  def base; 4; end
159
246
  def klass; Sudoku::S4_15; end
@@ -163,19 +250,35 @@ class S4_15Test < Test::Unit::TestCase
163
250
  end
164
251
  end
165
252
 
166
- class SnTest < Test::Unit::TestCase
167
- include GridTest
168
- include GeneratorTest
253
+ class SnTest < MyTestCase
254
+ include SudokuTest
169
255
 
170
256
  def base; 2; end
171
257
  def klass; Sudoku::Sn; end
172
258
  end
173
259
 
174
- class SudokuTest < Test::Unit::TestCase
260
+ class GlobalTest < Test::Unit::TestCase
175
261
  def test_autoclass
176
- [S3Test, S4_15Test, SnTest].each do |g|
177
- grid = g.new nil
178
- assert_equal grid.klass, Sudoku[grid.base]
262
+ {Sudoku::S3 => 3, Sudoku::S4_15 => 4, Sudoku::Sn => 2}.each do |klass, base|
263
+ assert_equal klass, Sudoku[base], "Sudoku[#{base}] => #{klass}"
264
+ end
265
+
266
+ Sudoku[2..7] = GridTest
267
+ assert_equal GridTest, Sudoku[6], "Definition d'autoclasses persos"
268
+ end
269
+
270
+ def test_speed
271
+ t = [Time.now]
272
+ klasses = [Sudoku::S3, Sudoku::S4_15, Sudoku::Sn]
273
+
274
+ klasses.each do |k|
275
+ s = k.new 3
276
+ 1000.times{|i| s.each{|x,y,v| s.valid? x,y,9}}
277
+ t << Time.now
278
+ end
279
+
280
+ 2.times do |i|
281
+ assert (t[i+1] - t[i])<(t[i+2] - t[i+1]), "#{klasses[i]} plus rapide que #{klasses[i+1]}"
179
282
  end
180
283
  end
181
284
  end