D
Dominik Bathon
Here is my solution.
It uses logic to deduce numbers for open cells which is enough for most =20
easy and intermediate sudokus (this is quite fast, usually <100ms), but i=
t =20
can also guess and backtrack to solve the hard ones and those with =20
multiple solutions.
The SudokuSolver class can solve sudokus for all valid square board sizes=
=20
(1x1, 4x4, 9x9, 16x16, ...), but the "if $0 =3D=3D __FILE__"-part only ha=
ndles =20
9x9 puzzles.
Dominik
The code:
class SudokuSolver
# sudoku is an array of arrays, containing the rows, which contain t=
he
# cells (all non valid entries are interpreted as open)
def initialize(sudoku)
# determine @n / @sqrt_n
@n =3D sudoku.size
@sqrt_n =3D Math.sqrt(@n).to_i
raise "wrong sudoku size" unless @sqrt_n * @sqrt_n =3D=3D @n
# populate internal representation
@arr =3D sudoku.collect { |row|
# ensure correct width for all rows
(0...@n).collect { |i|
# fixed cell or all values possible for open cell
((1..@n) =3D=3D=3D row) ? [row] : (1..@n).to_a
}
}
# initialize fix arrays
# they will contain all fixed cells for all rows, cols and boxes
@rfix=3DArray.new(@n) { [] }
@cfix=3DArray.new(@n) { [] }
@bfix=3DArray.new(@n) { [] }
@n.times { |r| @n.times { |c| update_fix(r, c) } }
# check for non-unique numbers
[@rfix, @cfix, @bfix].each { |fix| fix.each { |x|
unless x.size =3D=3D x.uniq.size
raise "non-unique numbers in row, col or box"
end
} }
end
# returns the internal representation as array of arrays
def to_a
@arr.collect { |row| row.collect { |x|
(x.size =3D=3D 1) ? x[0] : nil
} }
end
# returns a simple string representation
def to_s
fw =3D @n.to_s.size
to_a.collect { |row| row.collect { |x|
(x ? x.to_s : "_").rjust(fw)
}.join " " }.join "\n"
end
# returns whether the puzzle is solved
def finished?
@arr.each { |row| row.each { |x| return false if x.size > 1 } }
true
end
# for each cell remove the possibilities, that are already used in t=
he
# cell's row, col or box
# return if successful
def reduce
success =3D false
@n.times { |r| @n.times { |c|
if (sz =3D @arr[r][c].size) > 1
@arr[r][c] =3D @arr[r][c] -
(@rfix[r] | @cfix[c] | @bfix[rc2box(r, c)])
raise "impossible to solve" if @arr[r][c].empty?
# have we been successful
if @arr[r][c].size < sz
success =3D true
update_fix(r, c)
end
end
} }
success
end
# find open cells with unique elements in their row, col or box
# return if successful
# reduce must return false when this method is called (if the =20
possibilities
# aren't reduced, bad things may happen...)
def deduce
success =3D false
[:col_each, :row_each, :box_each].each { |meth|
@n.times { |i|
u =3D uniqs_in(meth, i)
unless u.empty?
send(meth, i) { |x|
if x.size > 1 && ((u2 =3D u & x).size =3D=3D 1)
success =3D true
u2
else
nil
end
}
# change only one row/col/box at a time
return success if success
end
}
}
success
end
# tries to solve the sudoku with reduce and deduce
# returns one of :impossible, :solved, :unknown
def solve
begin
until finished?
progress =3D false
while reduce
progress =3D true
end
progress =3D true if deduce
return :unknown unless progress
end
:solved
rescue
:impossible
end
end
# solves the sudoku using solve and if that fails, it tries to guess
# returns one of :impossible, :solved, :multiple_solutions
def backtrack_solve
if (res =3D solve) =3D=3D :unknown
# find first open cell
r, c =3D 0, 0
@rfix.each_with_index { |rf, r|
break if rf.size < @n
}
@arr[r].each_with_index { |x, c|
break if x.size > 1
}
partial =3D to_a
solutions =3D []
# try all possibilities for the open cell
@arr[r][c].each { |guess|
partial[r][c] =3D guess
rsolver =3D SudokuSolver.new(partial)
case rsolver.backtrack_solve
when :multiple_solutions
initialize(rsolver.to_a)
return :multiple_solutions
when :solved
solutions << rsolver
end
}
if solutions.empty?
return :impossible
else
initialize(solutions[0].to_a)
return solutions.size > 1 ? :multiple_solutions : :solve=
d
end
end
res
end
private
# returns the box index of row r and col c
def rc2box(r, c)
(r - (r % @sqrt_n)) + (c / @sqrt_n)
end
# if row r, col c contains a fixed cell, it is added to the fixed =20
arrays
def update_fix(r, c)
if @arr[r][c].size =3D=3D 1
@rfix[r] << @arr[r][c][0]
@cfix[c] << @arr[r][c][0]
@bfix[rc2box(r, c)] << @arr[r][c][0]
end
end
# yields each cell of row r and assigns the result of the yield unle=
ss =20
it
# is nil
def row_each(r)
@n.times { |c|
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# yields each cell of col c and assigns the result of the yield unle=
ss =20
it
# is nil
def col_each(c)
@n.times { |r|
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# yields each cell of box b and assigns the result of the yield unle=
ss =20
it
# is nil
def box_each(b)
off_r, off_c =3D (b - (b % @sqrt_n)), (b % @sqrt_n) * @sqrt_n
@n.times { |i|
r, c =3D off_r + (i / @sqrt_n), off_c + (i % @sqrt_n)
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# find unique numbers in possibility lists of a row, col or box
# each_meth must be :row_each, :col_each or :box_each
def uniqs_in(each_meth, index)
h =3D Hash.new(0)
send(each_meth, index) { |x|
x.each { |n| h[n] +=3D 1 } if x.size > 1
nil # we didn't change anything
}
h.select { |k, v| v =3D=3D 1 }.collect { |k, v| k }
end
end
if $0 =3D=3D __FILE__
# read a sudoku from stdin
sudoku =3D []
while sudoku.size < 9
row =3D gets.scan(/\d|_/).map { |s| s.to_i }
sudoku << row if row.size =3D=3D 9
end
# solve
begin
solver =3D SudokuSolver.new(sudoku)
puts "Input:", solver
case solver.backtrack_solve
when :solved
puts "Solution:"
when :multiple_solutions
puts "There are multiple solutions!", "One solution:"
else
puts "Impossible:"
end
puts solver
rescue =3D> e
puts "Error: #{e.message}"
end
end
It uses logic to deduce numbers for open cells which is enough for most =20
easy and intermediate sudokus (this is quite fast, usually <100ms), but i=
t =20
can also guess and backtrack to solve the hard ones and those with =20
multiple solutions.
The SudokuSolver class can solve sudokus for all valid square board sizes=
=20
(1x1, 4x4, 9x9, 16x16, ...), but the "if $0 =3D=3D __FILE__"-part only ha=
ndles =20
9x9 puzzles.
Dominik
The code:
class SudokuSolver
# sudoku is an array of arrays, containing the rows, which contain t=
he
# cells (all non valid entries are interpreted as open)
def initialize(sudoku)
# determine @n / @sqrt_n
@n =3D sudoku.size
@sqrt_n =3D Math.sqrt(@n).to_i
raise "wrong sudoku size" unless @sqrt_n * @sqrt_n =3D=3D @n
# populate internal representation
@arr =3D sudoku.collect { |row|
# ensure correct width for all rows
(0...@n).collect { |i|
# fixed cell or all values possible for open cell
((1..@n) =3D=3D=3D row) ? [row] : (1..@n).to_a
}
}
# initialize fix arrays
# they will contain all fixed cells for all rows, cols and boxes
@rfix=3DArray.new(@n) { [] }
@cfix=3DArray.new(@n) { [] }
@bfix=3DArray.new(@n) { [] }
@n.times { |r| @n.times { |c| update_fix(r, c) } }
# check for non-unique numbers
[@rfix, @cfix, @bfix].each { |fix| fix.each { |x|
unless x.size =3D=3D x.uniq.size
raise "non-unique numbers in row, col or box"
end
} }
end
# returns the internal representation as array of arrays
def to_a
@arr.collect { |row| row.collect { |x|
(x.size =3D=3D 1) ? x[0] : nil
} }
end
# returns a simple string representation
def to_s
fw =3D @n.to_s.size
to_a.collect { |row| row.collect { |x|
(x ? x.to_s : "_").rjust(fw)
}.join " " }.join "\n"
end
# returns whether the puzzle is solved
def finished?
@arr.each { |row| row.each { |x| return false if x.size > 1 } }
true
end
# for each cell remove the possibilities, that are already used in t=
he
# cell's row, col or box
# return if successful
def reduce
success =3D false
@n.times { |r| @n.times { |c|
if (sz =3D @arr[r][c].size) > 1
@arr[r][c] =3D @arr[r][c] -
(@rfix[r] | @cfix[c] | @bfix[rc2box(r, c)])
raise "impossible to solve" if @arr[r][c].empty?
# have we been successful
if @arr[r][c].size < sz
success =3D true
update_fix(r, c)
end
end
} }
success
end
# find open cells with unique elements in their row, col or box
# return if successful
# reduce must return false when this method is called (if the =20
possibilities
# aren't reduced, bad things may happen...)
def deduce
success =3D false
[:col_each, :row_each, :box_each].each { |meth|
@n.times { |i|
u =3D uniqs_in(meth, i)
unless u.empty?
send(meth, i) { |x|
if x.size > 1 && ((u2 =3D u & x).size =3D=3D 1)
success =3D true
u2
else
nil
end
}
# change only one row/col/box at a time
return success if success
end
}
}
success
end
# tries to solve the sudoku with reduce and deduce
# returns one of :impossible, :solved, :unknown
def solve
begin
until finished?
progress =3D false
while reduce
progress =3D true
end
progress =3D true if deduce
return :unknown unless progress
end
:solved
rescue
:impossible
end
end
# solves the sudoku using solve and if that fails, it tries to guess
# returns one of :impossible, :solved, :multiple_solutions
def backtrack_solve
if (res =3D solve) =3D=3D :unknown
# find first open cell
r, c =3D 0, 0
@rfix.each_with_index { |rf, r|
break if rf.size < @n
}
@arr[r].each_with_index { |x, c|
break if x.size > 1
}
partial =3D to_a
solutions =3D []
# try all possibilities for the open cell
@arr[r][c].each { |guess|
partial[r][c] =3D guess
rsolver =3D SudokuSolver.new(partial)
case rsolver.backtrack_solve
when :multiple_solutions
initialize(rsolver.to_a)
return :multiple_solutions
when :solved
solutions << rsolver
end
}
if solutions.empty?
return :impossible
else
initialize(solutions[0].to_a)
return solutions.size > 1 ? :multiple_solutions : :solve=
d
end
end
res
end
private
# returns the box index of row r and col c
def rc2box(r, c)
(r - (r % @sqrt_n)) + (c / @sqrt_n)
end
# if row r, col c contains a fixed cell, it is added to the fixed =20
arrays
def update_fix(r, c)
if @arr[r][c].size =3D=3D 1
@rfix[r] << @arr[r][c][0]
@cfix[c] << @arr[r][c][0]
@bfix[rc2box(r, c)] << @arr[r][c][0]
end
end
# yields each cell of row r and assigns the result of the yield unle=
ss =20
it
# is nil
def row_each(r)
@n.times { |c|
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# yields each cell of col c and assigns the result of the yield unle=
ss =20
it
# is nil
def col_each(c)
@n.times { |r|
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# yields each cell of box b and assigns the result of the yield unle=
ss =20
it
# is nil
def box_each(b)
off_r, off_c =3D (b - (b % @sqrt_n)), (b % @sqrt_n) * @sqrt_n
@n.times { |i|
r, c =3D off_r + (i / @sqrt_n), off_c + (i % @sqrt_n)
if (res =3D yield(@arr[r][c]))
@arr[r][c] =3D res
update_fix(r, c)
end
}
end
# find unique numbers in possibility lists of a row, col or box
# each_meth must be :row_each, :col_each or :box_each
def uniqs_in(each_meth, index)
h =3D Hash.new(0)
send(each_meth, index) { |x|
x.each { |n| h[n] +=3D 1 } if x.size > 1
nil # we didn't change anything
}
h.select { |k, v| v =3D=3D 1 }.collect { |k, v| k }
end
end
if $0 =3D=3D __FILE__
# read a sudoku from stdin
sudoku =3D []
while sudoku.size < 9
row =3D gets.scan(/\d|_/).map { |s| s.to_i }
sudoku << row if row.size =3D=3D 9
end
# solve
begin
solver =3D SudokuSolver.new(sudoku)
puts "Input:", solver
case solver.backtrack_solve
when :solved
puts "Solution:"
when :multiple_solutions
puts "There are multiple solutions!", "One solution:"
else
puts "Impossible:"
end
puts solver
rescue =3D> e
puts "Error: #{e.message}"
end
end