Ok, I've seen a couple other solutions posted. Here is my code. I threw
this together pretty quick.
I start out by populating the board matrix with either the given value
or an array of possibilities. We then go through each row, column and
box eliminating possibilities from the unknown squares. If there is
ever only one possibility for a square we set that square to the now
known value. Next we go through each row, column and box to find
instances where a square has multiple possibilities, but for that set
one of these possibilities is the only one. So if a square has
possibilities of 2 and 7, but no other square in the same row can be 2
then this square must be 2 and not 7. We alternate between this process
and eliminating until we either find a solution or we have gone through
a loop without finding anything to change.
I'm not convinced that this fully solves ever puzzle. I'd be interested
in a proof though. (ie if a given sudoku board has only one possible
solution then this algorithm will find it) The next logical step would
be to brute force the rest of the puzzle looking for solutions. A Depth
First Search would work.
In any case. This was a good help for me learning ruby. I was
especially happy to find the set operators for arrays. That made my
day.
-----Horndude77
#!/usr/bin/env ruby
class Sudoku
def initialize(boardstring)
@board = Array.new(9)
9.times { |i| @board[i] = Array.new(9) }
flattened = boardstring.delete("-+|\n").split
index = 0
@unknown = []
# set up actual array
9.times do |i|
9.times do |j|
if(flattened[index] == '_') then
@board[i][j] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
@unknown << [i,j]
else
@board[i][j] = flattened[index].to_i
end
index += 1
end
end
#set up what each row, col, and box contains
@rows = Array.new(9)
@cols = Array.new(9)
@boxes = Array.new(9)
9.times { |i| @rows[i] = numsInRow(i) }
9.times { |j| @cols[j] = numsInCol(j) }
3.times { |i| 3.times { |j| @boxes[i+3*j] = numsInBox(3*i,3*j) } }
end
def numsInRow(row)
toreturn = []
9.times do |j|
if(@board[row][j].kind_of? Fixnum) then
toreturn << @board[row][j]
end
end
return toreturn
end
def numsInCol(col)
toreturn = []
9.times do |i|
if(@board[i][col].kind_of? Fixnum) then
toreturn << @board[i][col]
end
end
return toreturn
end
def numsInBox(boxrow, boxcol)
toreturn = []
x = boxrow - boxrow%3
y = boxcol - boxcol%3
3.times do |i|
3.times do |j|
if(@board[x+i][y+j].kind_of? Fixnum) then
toreturn << @board[x+i][y+j]
end
end
end
return toreturn
end
def to_s
s = ""
9.times do |i|
if(i%3 == 0) then
s += "+-------+-------+-------+\n"
end
9.times do |j|
if(j%3 == 0) then
s += "| "
end
if(@board[i][j].kind_of? Array) then
s += "_ "
else
s += "#{@board[i][j]} "
end
end
s += "|\n"
end
s += "+-------+-------+-------+\n"
return s
end
# Looks in row, column and box to eliminate impossible values
def eliminate(i,j)
changed = false
if(@board[i][j].kind_of? Array) then
combined = @rows[i] | @cols[j] | @boxes[(i/3)+(j-j%3)]
if( (@board[i][j] & combined).length > 0) then
changed = true
@board[i][j] -= combined
end
if(@board[i][j].length == 1) then
foundsolution(i,j,@board[i][j][0])
end
end
return changed
end
def foundsolution(x,y,val)
@board[x][y] = val
@rows[x] << @board[x][y]
@cols[y] << @board[x][y]
@boxes[(x/3)+(y-y%3)] << @board[x][y]
@unknown.delete([x,y])
end
def eliminateall
changed = true
while(changed)
changed = false
@unknown.each do |u|
if(eliminate(u[0],u[1])) then changed = true end
end
end
return changed
end
#these check functions look for squares that have multiple
# possibilities except the set itself only has one.
def checkrow(i)
changed = false
set = Hash.new
9.times do |j|
if (@board[i][j].kind_of? Array) then
@board[i][j].each do |e|
if(set[e]) then
set[e] << j
else
set[e] = [j]
end
end
end
end
set.each do |k,v|
if(v.length == 1) then
foundsolution(i,v[0],k)
changed = true
end
end
return changed
end
def checkcol(j)
changed = false
set = Hash.new
9.times do |i|
if (@board[i][j].kind_of? Array) then
@board[i][j].each do |e|
if(set[e]) then
set[e] << i
else
set[e] = [i]
end
end
end
end
set.each do |k,v|
if(v.length == 1) then
foundsolution(v[0],j,k)
changed = true
end
end
return changed
end
def checkbox(n)
x = 3*(n%3)
y = 3*(n/3)
changed = false
set = Hash.new
3.times do |i|
3.times do |j|
if (@board[x+i][y+j].kind_of? Array) then
@board[x+i][y+j].each do |e|
if(set[e]) then
set[e] << [x+i,y+j]
else
set[e] = [ [x+i,y+j] ]
end
end
end
end
end
set.each do |k,v|
if(v.length == 1) then
foundsolution(v[0][0], v[0][1], k)
changed = true
end
end
return changed
end
def checkallrows
changed = false
9.times do |i|
if(checkrow(i)) then changed = true end
end
return changed
end
def checkallcols
changed = false
9.times do |j|
if(checkcol(j)) then changed = true end
end
return changed
end
def checkallboxes
changed = false
9.times do |n|
if(checkbox(n)) then changed = true end
end
return changed
end
def solve
#is there a better way to do this? it seems messy
# and redundant.
changed = true
while(changed && @unknown.length>0)
changed = false
changed = eliminateall ? true : changed
changed = checkallrows ? true : changed
changed = eliminateall ? true : changed
changed = checkallcols ? true : changed
changed = eliminateall ? true : changed
changed = checkallboxes ? true : changed
end
puts self
if(@unknown.length>0)
puts "I can't solve this one"
end
end
end
board = Sudoku.new($stdin.readlines.join)
board.solve