redsquirrel/nest

Bringing the sudoku solver over from gist

1 parent b4beedc commit 5f7041ecedb352e71893168c6de3e9c8ec0cdcad Dave Hoover committed Jan 2, 2009
Showing with 289 additions and 0 deletions.
1. +1 −0 sudoku/README
2. +25 −0 sudoku/board.rb
3. +52 −0 sudoku/brain.rb
4. +24 −0 sudoku/brain_cell.rb
5. +5 −0 sudoku/core_ext.rb
6. +21 −0 sudoku/grids.rb
7. +130 −0 sudoku/main.rb
8. +31 −0 sudoku/solver.rb
 @@ -0,0 +1 @@ +Original evolution of this solution can be found at http://gist.github.com/41670
 @@ -0,0 +1,25 @@ +class Board < Array + def initialize(raw) + super(raw.map { |l| l.chomp.split(//).map { |s| s.to_i } }) + end + + def blanks + blanks = [] + each_with_index do |row, y| + row.each_with_index do |cell, x| + if cell.zero? + blanks << [x, y] + end + end + end + blanks + end + + def set(x, y, value) + self[y][x] = value + end + + def report + map { |row| row.map { |c| c.zero? ? " " : c }.join("") }.join("\n") + end +end
 @@ -0,0 +1,52 @@ +class Brain < Array + def initialize + 9.times { |i| self[i] = (1..9).map { BrainCell.new([*1..9]) } } + end + + def narrow(blanks, board, grids) + blanks.each do |x, y| + brain_cell = self[y][x] + brain_cell.narrow(x, y, board, grids) + end + end + + def solvers(blanks) + solvers = Solvers.new + blanks.each do |x, y| + brain_cell = self[y][x] + if brain_cell.solved? + solvers << OpenStruct.new(:x => x, :y => y, :value => brain_cell.solved_value) + end + end + solvers + end + + def guess(board, blanks, state) + threads = [] + brain_cells = sorted_cells_with_coords_from(blanks) + brain_cells.each do |brain_cell, coords| + brain_cell.size.times do |i| + board[coords.last][coords.first] = brain_cell[i] + if state.finished + return + else + threads << Thread.new do + think(board, self, state) + end + end + end + board[coords.last][coords.first] = 0 + end + + end + +private + + def sorted_cells_with_coords_from(blanks) + blanks.map { |x, y| + [self[y][x], [x, y]] + }.sort_by { |brain_cell, coords| + brain_cell.size + } + end +end
 @@ -0,0 +1,24 @@ +# Would be nice to have coords in the BrainCell +class BrainCell < Array + class EmptyException < Exception; end + + def narrow(x, y, board, grids) + row = board[y] + col = board.map { |r| r[x] } + reject! { |n| row.include?(n) || col.include?(n) } + + grid = grids.get(x, y) + reject! { |n| grid.last.include?(n) } + + raise EmptyException if empty? + end + + def solved? + size == 1 + end + + def solved_value + raise "Not solved: #{self.join(", ")}" unless solved? + first + end +end
 @@ -0,0 +1,5 @@ +class Object + def deep_copy + Marshal.load( Marshal.dump( self ) ) + end +end
 @@ -0,0 +1,21 @@ +class Grids + SETUP = [[0..2, 0..2, []],[3..5, 0..2, []], [6..8, 0..2, []], [0..2, 3..5, []],[3..5, 3..5, []], [6..8, 3..5, []], [0..2, 6..8, []],[3..5, 6..8, []], [6..8, 6..8, []]] + + def initialize(board) + @grids = SETUP.deep_copy + # Figure out which numbers are in which grids + board.each_with_index do |row, y| + row.each_with_index do |cell, x| + @grids.each do |grid| + if !cell.zero? && grid[0].include?(x) && grid[1].include?(y) + grid.last << cell + end + end + end + end + end + + def get(x, y) + @grids.detect { |g| g[0].include?(x) && g[1].include?(y) } + end +end
 @@ -0,0 +1,130 @@ +require 'board' +require 'brain' +require 'brain_cell' +require 'core_ext' +require 'grids' +require 'ostruct' +require 'solver' + +def think(board, brain, state) + board = board.deep_copy + brain = brain.deep_copy + grids = Grids.new(board) + + blanks = board.blanks + if blanks.empty? + state.finished = board + return + end + + return if state.finished + + begin + brain.narrow(blanks, board, grids) + rescue BrainCell::EmptyException + return + end + + solvers = brain.solvers(blanks) + + if solvers.empty? + brain.guess(board, blanks, state) + + else + begin + solvers.check_for_conflicts(grids) + solvers.solve(board) + rescue Solver::ConflictException + return + end + end + + think(board, brain, state) +end + +# http://en.wikipedia.org/wiki/Sudoku +wiki = [ +"53 7 +6 195 + 98 6 +8 6 3 +4 8 3 1 +7 2 6 + 6 28 + 419 5 + 8 79", + +"534678912 +672195348 +198342567 +859761423 +426853791 +713924856 +961537284 +287419635 +345286179" +] + +# http://www.websudoku.com/?level=2&set_id=3350218628 +medium = [ +" 4 7 3 + 85 1 + 15 3 9 +5 7 21 + 6 8 + 81 6 9 + 2 4 57 + 7 29 + 5 7 8 ", + +"942187635 +368594127 +715236498 +593478216 +476921853 +281365749 +829643571 +137852964 +654719382" +] + +# http://www.websudoku.com/?level=4&set_id=470872047 +evil = [ +" 53 694 + 3 1 6 + 3 +7 9 + 1 3 2 + 2 7 + 6 +8 7 5 + 436 81 ", + +"285376941 +439125786 +176849235 +752981364 +618734529 +394562817 +567213498 +821497653 +943658172" +] + +[wiki, medium, evil].each do |start, solved| + + board = Board.new(start) + fail("Bad board") unless board.size == 9 && board[4].size == 9 + + brain = Brain.new + fail("Bad brain: rows=#{brain.size}, cols=#{brain[7].size}") unless brain.size == 9 && brain[7].size == 9 && brain[3][1].size == 9 + + state = OpenStruct.new + think(board, brain, state) + + if state.finished == Board.new(solved) + puts "PASS" + else + puts "FAIL\nExpected\n#{Board.new(solved).report}\nBut got\n#{state.finished.report}" + end +end
 @@ -0,0 +1,31 @@ +class Solvers < Array + class ConflictException < Exception; end + + def check_for_conflicts(grids) + each do |s1| + grid1 = grids.get(s1.x, s1.y) + + each do |s2| + next if s1.x == s2.x && s1.y == s2.y + + if s1.x == s2.x && s1.value == s2.value + raise ConflictException + end + if s1.y == s2.y && s1.value == s2.value + raise ConflictException + end + + grid2 = grids.get(s2.x, s2.y) + if grid1 == grid2 && s1.value == s2.value + raise ConflictException + end + end + end + end + + def solve(board) + each do |s| + board.set(s.x, s.y, s.value) + end + end +end