Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

update documentation

  • Loading branch information...
commit 76be47fedb619d3bcb2575c6f85a024813036725 1 parent d4f58dc
@shuber authored
View
38 README.rdoc
@@ -1,23 +1,26 @@
= sudoku
-solves square sudoku puzzles
+Solves traditional square Sudoku puzzles
+
+See http://en.wikipedia.org/wiki/Sudoku
== Usage
board = [
- [2, nil, nil, nil, nil, 1, nil, 3, 8],
- [nil, nil, nil, nil, nil, nil, nil, nil, 5],
- [nil, 7, nil, nil, nil, 6, nil, nil, nil],
- [nil, nil, nil, nil, nil, nil, nil, 1, 3],
- [nil, 9, 8, 1, nil, nil, 2, 5, 7],
- [3, 1, nil, nil, nil, nil, 8, nil, nil],
- [9, nil, nil, 8, nil, nil, nil, 2, nil],
- [nil, 5, nil, nil, 6, 9, 7, 8, 4],
- [4, nil, nil, 2, 5, nil, nil, nil, nil]
+ [ 2, nil, nil, nil, nil, 1, nil, 3, 8],
+ [nil, nil, nil, nil, nil, nil, nil, nil, 5],
+ [nil, 7, nil, nil, nil, 6, nil, nil, nil],
+ [nil, nil, nil, nil, nil, nil, nil, 1, 3],
+ [nil, 9, 8, 1, nil, nil, 2, 5, 7],
+ [ 3, 1, nil, nil, nil, nil, 8, nil, nil],
+ [ 9, nil, nil, 8, nil, nil, nil, 2, nil],
+ [nil, 5, nil, nil, 6, 9, 7, 8, 4],
+ [ 4, nil, nil, 2, 5, nil, nil, nil, nil]
]
- sudoku = Sudoku.new(board)
- puts sudoku
+ sudoku = Sudoku::Puzzle.new(board, :blank => '.')
+
+ puts sudoku.puzzle
# 2 . . . . 1 . 3 8
# . . . . . . . . 5
# . 7 . . . 6 . . .
@@ -28,7 +31,16 @@ solves square sudoku puzzles
# . 5 . . 6 9 7 8 4
# 4 . . 2 5 . . . .
- puts sudoku.solve
+ puts sudoku.solve!.puzzle
+ # 2 4 9 5 7 1 6 3 8
+ # 8 6 1 4 3 2 9 7 5
+ # 5 7 3 9 8 6 1 4 2
+ # 7 2 5 6 9 8 4 1 3
+ # 6 9 8 1 4 3 2 5 7
+ # 3 1 4 7 2 5 8 6 9
+ # 9 3 7 8 1 4 5 2 6
+ # 1 5 2 3 6 9 7 8 4
+ # 4 8 6 2 5 7 3 9 1
== Note on Patches/Pull Requests
View
4 Rakefile
@@ -5,8 +5,8 @@ begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = 'sudoku'
- gem.summary = 'solves square sudoku puzzles'
- gem.description = 'solves square sudoku puzzles'
+ gem.summary = 'Solves traditional square Sudoku puzzles'
+ gem.description = 'Solves traditional square Sudoku puzzles'
gem.email = 'shuber@huberry.com'
gem.homepage = 'http://github.com/shuber/sudoku'
gem.authors = ['Sean Huber']
View
139 lib/sudoku.rb
@@ -1,37 +1,121 @@
module Sudoku
- # Solves square Sudoku puzzles
+ # Solves traditional square Sudoku puzzles
#
# See http://en.wikipedia.org/wiki/Sudoku
+ #
+ # Example:
+ #
+ # unsolved_puzzle = [
+ # [ 2, nil, nil, nil, nil, 1, nil, 3, 8],
+ # [nil, nil, nil, nil, nil, nil, nil, nil, 5],
+ # [nil, 7, nil, nil, nil, 6, nil, nil, nil],
+ # [nil, nil, nil, nil, nil, nil, nil, 1, 3],
+ # [nil, 9, 8, 1, nil, nil, 2, 5, 7],
+ # [ 3, 1, nil, nil, nil, nil, 8, nil, nil],
+ # [ 9, nil, nil, 8, nil, nil, nil, 2, nil],
+ # [nil, 5, nil, nil, 6, 9, 7, 8, 4],
+ # [ 4, nil, nil, 2, 5, nil, nil, nil, nil]
+ # ]
+ #
+ # sudoku = Sudoku::Puzzle.new(unsolved_puzzle, :blank => '.')
+ #
+ # puts sudoku.puzzle
+ # # 2 . . . . 1 . 3 8
+ # # . . . . . . . . 5
+ # # . 7 . . . 6 . . .
+ # # . . . . . . . 1 3
+ # # . 9 8 1 . . 2 5 7
+ # # 3 1 . . . . 8 . .
+ # # 9 . . 8 . . . 2 .
+ # # . 5 . . 6 9 7 8 4
+ # # 4 . . 2 5 . . . .
+ #
+ # puts sudoku.solve!.puzzle
+ # # 2 4 9 5 7 1 6 3 8
+ # # 8 6 1 4 3 2 9 7 5
+ # # 5 7 3 9 8 6 1 4 2
+ # # 7 2 5 6 9 8 4 1 3
+ # # 6 9 8 1 4 3 2 5 7
+ # # 3 1 4 7 2 5 8 6 9
+ # # 9 3 7 8 1 4 5 2 6
+ # # 1 5 2 3 6 9 7 8 4
+ # # 4 8 6 2 5 7 3 9 1
class Puzzle < Array
alias_method :columns, :transpose
- def initialize(rows, options = {})
- super rows
+ # Accepts a puzzle as an array of rows and an optional hash of options
+ #
+ # The options are:
+ #
+ # :values - An array of values to fill the puzzle with. Defaults to an array
+ # containing the numbers 1 to the size of the puzzle. For example,
+ # a 9x9 puzzle would have [1,2,3,4,5,6,7,8,9] as its default values.
+ # :blank - The string to use for nil values when printing out the puzzle as a string.
+ # Defaults to a period.
+ #
+ # Example:
+ #
+ # unsolved_puzzle = [
+ # [ 2, nil, nil, nil, nil, 1, nil, 3, 8],
+ # [nil, nil, nil, nil, nil, nil, nil, nil, 5],
+ # [nil, 7, nil, nil, nil, 6, nil, nil, nil],
+ # [nil, nil, nil, nil, nil, nil, nil, 1, 3],
+ # [nil, 9, 8, 1, nil, nil, 2, 5, 7],
+ # [ 3, 1, nil, nil, nil, nil, 8, nil, nil],
+ # [ 9, nil, nil, 8, nil, nil, nil, 2, nil],
+ # [nil, 5, nil, nil, 6, 9, 7, 8, 4],
+ # [ 4, nil, nil, 2, 5, nil, nil, nil, nil]
+ # ]
+ # sudoku = Sudoku::Puzzle.new(unsolved_puzzle, :blank => 'x')
+ def initialize(puzzle, options = {})
+ super puzzle
self.options.merge!(options)
validate_arguments!
end
+ # Returns the values associated with a column
def column(index)
columns[index]
end
+ # Returns the size of the longest member in optinos[:values]
+ # This is used by <tt>puzzle</tt> to make sure the columns are printed out evenly
+ def max_value_size
+ @max_value_size ||= options[:values].max { |a, b| a.size <=> b.size }
+ end
+
+ # Returns a hash of options for this instance
def options
@options ||= { :values => (1..size).to_a, :blank => '.' }
end
- def max_value_size
- @max_value_size ||= options[:values].max { |a, b| a.size <=> b.size }
+ # Returns a string representation of the puzzle
+ def puzzle
+ inject([]) { |rows, row| rows << row.map { |value| (value || options[:blank]).to_s.rjust(max_value_size) }.join(' ') }.join("\n")
end
+ # Returns the values associated with a row
def row(index)
self[index]
end
+ # Returns the size of this puzzle's sections
+ #
+ # Example:
+ #
+ # sudoku = Sudoku::Puzzle.new(puzzle_9x9)
+ # sudoku.section_size # => 3
def section_size
@section_size ||= Math.sqrt(size).to_i
end
+ # Returns an array of coordinates corresponding to each section
+ #
+ # Example:
+ #
+ # sudoku = Sudoku::Puzzle.new(puzzle_9x9)
+ # sudoku.sections[0] # => [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]
def sections
(1..section_size).inject([]) do |array, row_offset|
(1..section_size).inject(array) do |array, column_offset|
@@ -40,28 +124,43 @@ def sections
end
end
- # Solves this instance's board, returns self
+ # Solves this instance's puzzle, returns self
def solve!
solve
self
end
- # Returns a string representation of the board
- def to_s
- inject([]) { |rows, row| rows << row.map { |value| (value || options[:blank]).to_s.rjust(max_value_size) }.join(' ') }.join("\n")
- end
-
+ # Checks if this puzzle has been solved correctly
def valid?
1.upto(size) { |index| return false unless valid_row?(index - 1) && valid_column?(index - 1) }
sections.all? { |section| valid_section?(section) }
end
+ # Checks if all values are filled in and unique for the column at the specified <tt>index</tt>.
+ def valid_column?(index)
+ column_values = column(index).compact
+ column_values == column_values.uniq
+ end
+
+ # Checks if all values are filled in and unique for the row at the specified <tt>index</tt>.
+ def valid_row?(index)
+ row_values = self[index].compact
+ row_values == row_values.uniq
+ end
+
+ # Checks if all values are filled in and unique in the array of coordinates specified.
+ def valid_section?(section)
+ values(section).compact.sort == options[:values].sort
+ end
+
+ # Returns an array of the values associated with an array of coordinates
def values(coords)
coords.collect { |coord| self[coord[0]][coord[1]] }
end
protected
+ # Returns an array of coordinates for the section starting at the specified <tt>row_offset</tt> and <tt>column_offset</tt>
def section_coordinates(row_offset, column_offset)
row_start = row_offset * section_size
row_end = row_start + section_size - 1
@@ -70,7 +169,7 @@ def section_coordinates(row_offset, column_offset)
((row_start..row_end).to_a * section_size).sort.zip((column_start..column_end).to_a * section_size)
end
- def solve
+ def solve #:nodoc:
sorted_sections = sections.sort { |a, b| values(a).nitems <=> values(b).nitems }.reverse!
failing_values = Array.new(size, nil)
missing_values = sorted_sections.collect { |section| options[:values].reject { |value| values(section).include?(value) } }
@@ -109,25 +208,11 @@ def solve
end
end
- def validate_arguments!
+ def validate_arguments! #:nodoc:
raise ArgumentError.new('Puzzle size must be a square - e.g. 4x4, 9x9, 16x16, 25x25') unless section_size == Math.sqrt(size) && size == columns.size
raise ArgumentError.new("Must specify #{size} values for a #{size}x#{size} puzzle") unless size == options[:values].size
raise ArgumentError.new('Must specify unique values in options[:values]') unless options[:values] == options[:values].uniq
end
- def valid_column?(index)
- column_values = column(index).compact
- column_values == column_values.uniq
- end
-
- def valid_row?(index)
- row_values = self[index].compact
- row_values == row_values.uniq
- end
-
- def valid_section?(section)
- values(section).compact.sort == options[:values].sort
- end
-
end
end
View
93 lib/sudoku/board.rb
@@ -1,93 +0,0 @@
-class Sudoku
- class Board < Array
-
- alias_method :columns, :transpose
-
- def initialize(rows, options = {})
- super rows
- self.options.merge!(options)
- validate_arguments!
- end
-
- def column(index)
- columns[index]
- end
-
- def options
- @options ||= { :values => (1..size).to_a, :blank => '.' }
- end
-
- def max_value_size
- @max_value_size ||= options[:values].max { |a, b| a.size <=> b.size }
- end
-
- def row(index)
- self[index]
- end
-
- def section(row_offset, column_offset)
- row_start = row_offset * section_size
- row_end = row_start + section_size - 1
- column_start = column_offset * section_size
- column_end = column_start + section_size - 1
- coordinates = ((row_start..row_end).to_a * section_size).sort.zip((column_start..column_end).to_a * section_size)
- coordinates.inject([]) { |array, (row, column)| array << self[row][column] }
- end
-
- def section_size
- @section_size ||= Math.sqrt(size).to_i
- end
-
- def sections
- (1..section_size).inject([]) do |array, row_offset|
- (1..section_size).inject(array) do |array, column_offset|
- array << section(row_offset - 1, column_offset - 1)
- end
- end
- end
-
- def to_s
- inject([]) { |rows, row| rows << row.map { |value| (value || options[:blank]).to_s.rjust(max_value_size) }.join(' ') }.join("\n")
- end
-
- def valid?
- all? { |row| valid_row?(row) } && columns.all? { |column| valid_column?(column) } && sections.all? { |section| valid_section?(section) }
- end
-
- protected
-
- def validate_arguments!
- raise ArgumentError.new('Board size must be a square - e.g. 4x4, 9x9, 16x16, 25x25') unless section_size == Math.sqrt(size) && size == columns.size
- raise ArgumentError.new("Must specify #{size} values for a #{size}x#{size} board") unless size == options[:values].size
- raise ArgumentError.new('Must specify unique values in options[:values]') unless options[:values] == options[:values].uniq
- end
-
- def valid_column?(column)
- column == column.compact.uniq!
- end
-
- def valid_row?(row)
- row == row.compact.uniq!
- end
-
- def valid_section?(section)
- section.sort == options[:values].sort
- end
-
- end
-end
-
-board = [
- [2, nil, nil, nil, nil, 1, nil, 3, 8 ],
- [nil, nil, nil, nil, nil, nil, nil, nil, 5 ],
- [nil, 7, nil, nil, nil, 6, nil, nil, nil],
- [nil, nil, nil, nil, nil, nil, nil, 1, 3 ],
- [nil, 9, 8, 1, nil, nil, 2, 5, 7 ],
- [3, 1, nil, nil, nil, nil, 8, nil, nil],
- [9, nil, nil, 8, nil, nil, nil, 2, nil],
- [nil, 5, nil, nil, 6, 9, 7, 8, 4 ],
- [4, nil, nil, 2, 5, nil, nil, nil, nil]
-]
-
-b = Sudoku::Board.new(board)
-puts b.sections.inspect
View
4 test/sudoku_test.rb
@@ -3,8 +3,8 @@
class SudokuTest < Test::Unit::TestCase
should "probably rename this file and start testing for real" do
sudoku = Sudoku::Puzzle.new(Sudoku::Puzzles.first)
- puts sudoku.to_s
+ puts sudoku.puzzle
puts ''
- puts sudoku.solve!.to_s
+ puts sudoku.solve!.puzzle
end
end
View
12 test/test_helper.rb
@@ -32,5 +32,17 @@
]
]
+Sudoku::InvalidPuzzle = [
+ [ 2, 2, nil, nil, nil, 1, nil, 3, 8],
+ [nil, nil, nil, nil, nil, nil, nil, nil, 5],
+ [nil, 7, nil, nil, nil, 6, nil, nil, nil],
+ [nil, nil, nil, nil, nil, nil, nil, 1, 3],
+ [nil, 9, 8, 1, nil, nil, 2, 5, 7],
+ [ 3, 1, nil, nil, nil, nil, 8, nil, nil],
+ [ 9, nil, nil, 8, nil, nil, nil, 2, nil],
+ [nil, 5, nil, nil, 6, 9, 7, 8, 4],
+ [ 4, nil, nil, 2, 5, nil, nil, nil, nil]
+]
+
class Test::Unit::TestCase
end
Please sign in to comment.
Something went wrong with that request. Please try again.