audibleblink
8/19/2014 - 2:56 AM

Easy-level solver for sudoku

Easy-level solver for sudoku

class Sudoku

  def initialize(board_string)
    @board = board_string.split("")
  end

  def solve!
    return self if solved?
    board.each_with_index do |cell, cell_index|
      # next unless cell == '0'
      replace_cell_if_only_answer_at cell_index if cell == "0"
    end
    solve! # did this instead of #until because I prefer a dead stack to an infinite loop, just in case
  end


  private 

  attr_accessor :board # made this private because I never need to call #board outside this class

  def solved?
    !board.include?('0')
  end

  def replace_cell_if_only_answer_at index
    candidates = possible_answers_at index
    board[index] = candidates.first if candidates.length == 1
  end

  def possible_answers_at index
    #useful article on array manipulation http://blog.endpoint.com/2011/06/using-set-operators-with-ruby-arrays.html
    [*"1".."9"] - ( row(index) | col(index) | box(index) ) 
  end


# These methods return a single array from 
# their collections for a given spot on the board

  def row index
    rows[index / 9]
  end

  def col index
    columns[index % 9]
  end

  def box index
    boxes[ find_box_index_for(index) ]
  end


# These methods return 2D arrays of our board's 
# rows, columns and boxes

  def rows
    board.each_slice(9).to_a
  end

  def columns
    board.each_slice(9).to_a.transpose
  end

  def boxes
    boxes = Array.new(9) { Array.new }
    board.each_with_index do |cell, cell_index|
      boxes[ find_box_index_for(cell_index) ].push cell
    end
    boxes
  end
  
  def find_box_index_for index
    (index / 9 / 3 * 3) + (index % 9 / 3)
  end
 
  def to_s
    board.each_slice(9).to_a.map { |row| row.join(' ') }.join("\n")
  end

end


# The file has newlines at the end of each line, so we call
# String#chomp to remove them.

board_string = File.readlines('sample.unsolved.txt').first.chomp
puzzle = Sudoku.new(board_string)
puts puzzle.solve!