Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Bringing the sudoku solver over from gist

  • Loading branch information...
commit 5f7041ecedb352e71893168c6de3e9c8ec0cdcad 1 parent b4beedc
Dave Hoover authored
View
1  sudoku/README
@@ -0,0 +1 @@
+Original evolution of this solution can be found at http://gist.github.com/41670
View
25 sudoku/board.rb
@@ -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
View
52 sudoku/brain.rb
@@ -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
View
24 sudoku/brain_cell.rb
@@ -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
View
5 sudoku/core_ext.rb
@@ -0,0 +1,5 @@
+class Object
+ def deep_copy
+ Marshal.load( Marshal.dump( self ) )
+ end
+end
View
21 sudoku/grids.rb
@@ -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
View
130 sudoku/main.rb
@@ -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
View
31 sudoku/solver.rb
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.