--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;
	nameod.rb"
Content-Disposition: attachment;
	filenamed.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;
	nameod_test.rb"
Content-Disposition: attachment;
	filenamed_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;
	nameodoku.rb"
Content-Disposition: attachment;
	filenamedoku.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 "> FOUND A SOLUTION !!"
    puts sod.to_s
    
# and if not, set a small-options cell to each of its options in turn (in a clone)...    
  else
    sod.cell(smallest[0], smallest[1]).alloweds.each_byte{|v|
      tempsod  od.clone
      tempsod.setcell(smallest[0], smallest[1], v.chr.to_i)
# ... and call ourselves recursively to solve the clone      
      solve(tempsod)
    }
  end
end

# Main Program 
@testdata  <HERE
'+-------+-------+-------+
| _ 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 _ |
+-------+-------+-------+'
HERE
  
sod  od.new(@testdata)
puts "Solving... \n#{sod.to_s} \n"

solve sod

puts 'Press <ENTER> to continue...'
gets

--Apple-Mail-3-725309915
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset-ASCII;
	format­´wed



--Apple-Mail-3-725309915--