sudokuhandler 0.1.2 → 0.1.3

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