--Apple-Mail-3-725309915 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset -ASCII; formatðïwed Begin forwarded message: > From: gdefty / attglobal.net > Date: August 25, 2005 10:13:34 PM CDT > To: james / grayproductions.net, > > > James, > > My stab at a sudoku solver. A bit rough roung the > edges (I have a job to do!) but it works. > > It seemed to me that basic algorithm and by > association, data representation were the key > factors here, and I was reasonably pleased with my > choices (until I see some of the other solutions > and realise what a dummy I was somewhere :-) > > Where I know I will be disappointed is in the > ruby-ness of my code (or rather, lack of it :-( > Mine always seems a bit pedestrian compared to > some of the elegant stuff you show. Sigh. I guess > I have been doing this too long. I'm probably > writing a COBOL solution in Ruby! Still, learning > is the whole point, no? > > Anyway, I hope you find something of interest in > it, and thanks again for a great site. > > Regards, > > graeme --Apple-Mail-3-725309915 Content-Transfer-Encoding: 7bit Content-Type: application/octet-stream; x-unix-mode66; name od.rb" Content-Disposition: attachment; filename d.rb # A number of design decisions (and some alternatives) # 1. Store 'available numbers' # a) at the cell level and make them unavailable to # other cells in the row/col/block when one was set # b) at the row/col/block level and check these items each # time a I needed to select a new cell value # I chose a), as I figured that there would be more time spent # checking than setting and unsetting. I think (without proof) # that this was a good choice # 2. Store available numbers for a given cell as # a) a hash of the available keys # b) an array of 9 'true' and 'false' values # I chose b), and I think it was probably not the best choice. # you can see from the code that I have to access this array # in different ways which I suspect would have been easier with a hash. # 3. Encapsulating the structure of the source data format was a # 'spur-of-the-moment' refactoring decision which just seemed like a good # idea at the time, and turned out to be so when I had to override # the 'clone' method for Sods # The basic class to hold the value in a single location in the puzzle class SodCell attr_reader :val # can be initialised with a value, but in the event I didn't use this def initialize(val) @val al.to_i if @val 0 @allowed rray.new(10, true) else @allowed rray.new(10, false) @allowed[@val] rue end end # set the cell to a given value def set(x) @val end # unset the cell back to empty def unset() rslt val @val return rslt end # is the cell is allowed to contain 'x'? def allowed(x) @allowed[x] end # what values are allowed? # This is mostly to determine *how many* values are allowed # and is the reason I think a Hash would have been better def alloweds rslt ' @allowed.each_with_index{|v, i| rslt + ? i.to_s : '' } return rslt end # add 'x' to the list of allowed values def allow(x) @allowed[x] rue end # drop 'x' from the list of allowed values def deny(x) @allowed[x] alse end # create a printable representation of the cell (for debugging) def to_s rslt al.to_s + ':' @allowed.each_with_index{|v, i| rslt + ? i.to_s : '.' } return rslt end end # A wrapper class round the input data # Only one method 'each' which unravels the values # and returns them one at a time. # Allows some flexibility in the input format (its's a feature ;-) class SodSrc def initialize(src) @src rc end def each rowsrc src.split("\n") rownum rowsrc.each{ |r| colnum if r /\d|'_'/ then while r /.*?([\d_])(.*)/ yield(rownum, colnum, $1) colnum + r 2 end rownum + end } end end # The main class representing a puzzle class Sod # can be created from a string representing a position, # or from another Sod. def initialize(srcdata) @cells ] 99.times{|i| @cells << SodCell.new('') } if srcdata.class String then srcdata odSrc.new(srcdata) end srcdata.each{|row, col, val| setcell(row, col, val.to_i) } end # return the cell at a given location def cell(row, col) @cells[row*10+col] end # yield all the cells in a row def each_in_row(row) for col in 0..8 yield cell(row, col) end end # yield all the cells in a column def each_in_col(col) for row in 0..8 yield cell(row, col) end end # yield all the cells in a 3x3 block def each_in_block(row, col) baserow ow/3*3 basecol ol/3*3 for row in baserow..(baserow+2) for col in basecol..(basecol+2) yield cell(row, col) end end end # yield all the cells in the puzzle. A bit different from the # other 'each...' methods in that it returns the location and # value of the cell, instead of the cell itself. This is needed # to make it work in 'initialize' (when cloning) which is the # only place it is used def each() for row in 0..8 for col in 0..8 yield(row, col, cell(row, col).val) end end end # set a given cell to a value and adjust all others in the row, column # and block to deny them the use of that value def setcell (row, col, val) cell(row, col).set(val) # set the cell value each_in_row(row){|c| # and deny that value to others in the row ... c.deny(val) } each_in_col(col){|c| # ... and column ... c.deny(val) } each_in_block(row, col){|c| # ... and block c.deny(val) } end # reset a given cell and adjust all others in the row, column and block to # allow them the use of that cell's value def unsetcell (row, col) val ell(row, col).unset()# unset the cell value each_in_row(row){|c| # and allow that value to others in the row ... c.allow(val) } each_in_col(col){|c| # ... and column ... c.allow(val) } each_in_block(row, col){|c| # ... and block c.allow(val) } end # Create a 'deep copy' clone of myself # Needed because Object#clone creates a 'shallow' copy def clone Sod.new(self) end # Generate the printable format def to_s rowconst +-------+-------+-------+' rslt rowconst] for blockrow in 0..2 for relrow in 0..2 rowdat |' for blockcol in 0..2 for relcol in 0..2 rowdat + ' + cell(blockrow*3 + relrow, blockcol*3 + relcol).val.to_s end rowdat + |' end rslt << rowdat end rslt << rowconst end rslt slt.join("\n") rslt.gsub('0', '_') end # Dump the puzzle contents (for debugging only) def dump for row in 0..8 for col in 0..8 if cell(row, col).val 0 then puts "(#{row.to_s},#{col.to_s}) cell(row,col).to_s}" end end end end end --Apple-Mail-3-725309915 Content-Transfer-Encoding: 7bit Content-Type: application/octet-stream; x-unix-mode66; name od_test.rb" Content-Disposition: attachment; filename d_test.rb require 'test/unit' require 'Sod.rb' class TC_Cell < Test::Unit::TestCase def setup end def test_setup c odCell.new('3') assert_equal(3, c.val) c odCell.new('_') assert_equal(0, c.val) end def test_set_unset c odCell.new('3') assert_equal(3, c.val) c.unset() assert_equal(0, c.val) c.set(7) assert_equal(7, c.val) end def test_allow_deny c odCell.new('5') assert_equal(true, c.allowed(5)) assert_equal(false, c.allowed(3)) c.allow(3) assert_equal(true, c.allowed(3)) c.deny(3) assert_equal(false, c.allowed(3)) c odCell.new('_') assert_equal(true, c.allowed(5)) assert_equal(true, c.allowed(3)) end def test_to_s c odCell.new('') assert_equal('0:0123456789', c.to_s) c.deny(3) c.deny(4) c.deny(9) assert_equal('0:012..5678.', c.to_s) end end class TC_SodSrc < Test::Unit::TestCase def test_setup @testdata +-------+-------+-------+ | _ 6 _ | 1 8 ---- | 2 ----' src odSrc.new(@testdata) rslt ' src.each{|row, col, val| rslt + |' + row.to_s + ',' + col.to_s + ',' + val.to_s } assert_equal('|0,0,_|0,1,6|0,2,_|0,3,1|1,0,8|2,0,2', rslt) end end class TC_Sod < Test::Unit::TestCase def setup @testdata +-------+-------+-------+ | _ 6 _ | 1 _ 4 | _ 5 _ | | _ _ 8 | 3 _ 5 | 6 _ _ | | 2 _ _ | _ _ _ | _ _ 1 | +-------+-------+-------+ | 8 _ _ | 4 _ 7 | _ _ 6 | | _ _ 6 | _ _ _ | 3 _ _ | | 7 _ _ | 9 _ 1 | _ _ 4 | +-------+-------+-------+ | 5 _ _ | _ _ _ | _ _ 2 | | _ _ 7 | 2 _ 6 | 9 _ _ | | _ 4 _ | 5 _ 8 | _ 7 _ | +-------+-------+-------+' end def test_setup sod od.new(@testdata) assert_equal(6, sod.cell(0,1).val) assert_equal(2, sod.cell(2,0).val) assert_equal(7, sod.cell(8,7).val) assert_equal(@testdata, sod.to_s) # test to_s assert_equal('1:..23......', sod.cell(5,5).to_s) # test cell 'allowed's assert_equal('23', sod.cell(5,5).alloweds) assert_equal('0:...34...89', sod.cell(2,7).to_s) # sod.dump end def test_each_in_row sod od.new(@testdata) rslt ' sod.each_in_row(3){|c| rslt + .val.to_s } assert_equal('800407006', rslt) end def test_each_in_col sod od.new(@testdata) rslt ' sod.each_in_col(2){|c| rslt + .val.to_s } assert_equal('080060070', rslt) end def test_each_in_block sod od.new(@testdata) rslt ' sod.each_in_block(7,1){|c| rslt + .val.to_s } assert_equal('500007040', rslt) end def test_set_unset sod od.new(@testdata) sod.setcell(2, 6, 7) assert_equal(7, sod.cell(2, 6).val) assert_equal(false, sod.cell(2, 5).allowed(7)) # check the row deny assert_equal(false, sod.cell(5, 6).allowed(7)) # and the col deny assert_equal(false, sod.cell(1, 7).allowed(7)) # and the block deny sod.unsetcell(2, 6) assert_equal(0, sod.cell(2, 6).val) assert_equal(true, sod.cell(2, 5).allowed(7)) # check the row allow assert_equal(true, sod.cell(5, 6).allowed(7)) # and the col allow assert_equal(true, sod.cell(1, 7).allowed(7)) # and the block allow end end --Apple-Mail-3-725309915 Content-Transfer-Encoding: 7bit Content-Type: application/octet-stream; x-unix-mode66; name odoku.rb" Content-Disposition: attachment; filename doku.rb require 'Sod.rb' # The main solver of puzzles. The algorithm is straightforward # 1. Cycle through the grid looking for cells that can only take # one value, and set them to that value. # 2. This will restrict the values of other cells, so keep cycling thru # until all cells either are fixed or can take more than one value. # 3. (While this cycling is going on, also look for the cell which is unset # and also has the smallest list of possible values.) # 4. If there is NO smallest, all cells must already be set. That means # we have found a solution, so print it. # 5. Otherwise, cycle through the list of values that the chosen smallest # cell can take and for each one, # - create a clone of the puzzle as it currently stands, # - set the value of the cell to that value and # - call 'solve' again recursively, passing it the clone. # 6. If more than one solution exists, this will find them all # 7. If no solution exists, none will be printed ;-) # 8. If the original puzzle was ill-formed, results are unpredictable. def solve(sod) puts "\n\nSolver called to solve :\n#{sod.to_s}" if $VERBOSE # set all the cells that can be determined from the initial conditions begin fixed smallestval 9 smallest ] for row in 0..8 for col in 0..8 c od.cell(row, col) if c.val 0 if c.alloweds.size < smallestval.size smallest row,col] smallestval .alloweds end if c.alloweds.size 1 sod.setcell(row,col,c.alloweds.to_i) puts "(#{row},#{col}) set to #{c.val}" if $VERBOSE fixed + end end end end puts "#{fixed} were fixed" if $VERBOSE end while fixed > 0 puts "\nSolver bottomed out at:\n#{sod.to_s}" if $VERBOSE puts "Smallest found was (#{smallest[0]},#{smallest[1]}) {smallestval}" if $VERBOSE # If we got a solution, print it out if smallest [] puts "