Skip to content
This repository was archived by the owner on Jun 8, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# A sample Gemfile
source "https://rubygems.org"

gem "mocha"
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
GEM
remote: https://rubygems.org/
specs:
metaclass (0.0.4)
mocha (1.0.0)
metaclass (~> 0.0.1)

PLATFORMS
ruby

DEPENDENCIES
mocha
93 changes: 93 additions & 0 deletions lib/sudoku_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
class SudokuFileParser
attr_reader :grid

def initialize(filename)
contents = IO.read(filename)
stripped = strip_unnecessary(contents)
@grid = gridify(stripped)
end

private

def strip_unnecessary(contents)
contents.gsub(/[^\d\.]/, '')
end

def gridify(stripped)
stripped.chars.each_slice(9).to_a
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If later you needed to allow differently sized grids (e.g., 16x16), hard-coding the grid size would make it more difficult. Whether a more-general solution is best would depend on how likely this prospect is, though.

end
end


class RowValidator
def initialize(grid)
@stripped_grid = strip_dots(grid)
end

def valid?
@stripped_grid.all? { |row| row.uniq.size == row.size }
end

private

def strip_dots(grid)
grid.map do |row|
row.reject { |e| e == '.' }
end
end
end


class ColumnValidator
def initialize(grid)
@validator = RowValidator.new(grid.transpose)
end

def valid?
@validator.valid?
end
end


class SubgridValidator
def initialize(grid)
modified_grid = subgrids_to_rows(grid)
@validator = RowValidator.new(modified_grid)
end

def valid?
@validator.valid?
end

private

def subgrids_to_rows(grid)
grid.each_slice(3).each_with_object([]) do |rows, new_grid|
new_grid.push(*row_of_subgrids(rows))
end
end

def row_of_subgrids(rows)
rows.flatten
.each_slice(3) # in triples
.group_by.with_index { |_, i| i % 3 } # group every third
.map { |i, e| e.flatten } # form a row from subgrid elements
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I understood what group_by.with_index and all the other methods were doing, your solution was relatively easy to follow. Good job!

end


class SudokuValidator
def initialize(filename)
@grid = SudokuFileParser.new(filename).grid
@complete = !@grid.flatten.any? { |e| e == '.' }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use none?, you could get rid of the !.

end

def valid?
validators = [RowValidator, ColumnValidator, SubgridValidator]
validators.all? { |v| v.new(@grid).valid? }
end

def complete?
@complete
end
end
12 changes: 12 additions & 0 deletions sudoku-validator
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env ruby

require_relative 'lib/sudoku_validator'

app = SudokuValidator.new(ARGV.first)

if app.valid?
ending = app.complete? ? '.' : ', but incomplete.'
puts "This sudoku is valid#{ending}"
else
puts 'This sudoku is invalid.'
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repetition of "This sudoku is " makes your solution easier to follow than my solution was.

File renamed without changes.
130 changes: 130 additions & 0 deletions test/sudoku_validator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
require 'minitest/autorun'
require 'minitest/pride'
require 'mocha/mini_test'

require_relative '../lib/sudoku_validator'

class SudokuFileParserTest < MiniTest::Unit::TestCase
def test_reads_file_into_9x9_2d_array
parser = SudokuFileParser.new('test/valid_complete.sudoku')

assert_equal(9, parser.grid.size)
parser.grid.each { |row| assert_equal(9, row.size) }
end

def test_parses_as_strings
expected =
[%w{8 5 . . . 2 4 . .},
%w{7 2 . . . . . . 9},
%w{. . 4 . . . . . .},
%w{. . . 1 . 7 . . 2},
%w{3 . 5 . . . 9 . .},
%w{. 4 . . . . . . .},
%w{. . . . 8 . . 7 .},
%w{. 1 7 . . . . . .},
%w{. . . . 3 6 . 4 .}]

parser = SudokuFileParser.new('test/valid_incomplete.sudoku')
assert_equal(expected, parser.grid)
end
end

class RowValidatorTest < MiniTest::Unit::TestCase
def test_valid_when_unique_in_row
grid = [%w{1 2 3}, %w{3 2 1}]

validator = RowValidator.new(grid)

assert(validator.valid?)
end

def test_invalid_when_duplicates_in_row
grid = [%w{1 2 3}, %w{3 2 3}]

validator = RowValidator.new(grid)

refute(validator.valid?)
end

def test_ignore_dots
assert(RowValidator.new([['.', '.'], ['.', '.']]).valid?)
assert(RowValidator.new([['.', '.'], ['1', '.']]).valid?)
refute(RowValidator.new([['.', '.', '2'], ['1', '.', '1']]).valid?)
end
end

class ColumnValidatorTest < MiniTest::Unit::TestCase
def test_is_implemented_in_terms_of_row_validator_for_transposed_grid
grid =[%w{1 2 3}, %w{4 5 6}]
row_validator = mock()
row_validator.expects(:valid?).returns(true)
RowValidator
.expects(:new)
.with([%w{1 4}, %w{2 5}, %w{3 6}])
.returns(row_validator)

ColumnValidator.new(grid).valid?
end
end

class SubgridValidatorTest < MiniTest::Unit::TestCase
def test_is_implemented_in_terms_of_row_validator_for_modified_grid
grid =
[%w{8 5 . . . 2 4 . .},
%w{7 2 . . . . . . 9},
%w{. . 4 . . . . . .},
%w{. . . 1 . 7 . . 2},
%w{3 . 5 . . . 9 . .},
%w{. 4 . . . . . . .},
%w{. . . . 8 . . 7 .},
%w{. 1 7 . . . . . .},
%w{. . . . 3 6 . 4 .}]

modified_grid =
[%w{8 5 . 7 2 . . . 4},
%w{. . 2 . . . . . .},
%w{4 . . . . 9 . . .},
%w{. . . 3 . 5 . 4 .},
%w{1 . 7 . . . . . .},
%w{. . 2 9 . . . . .},
%w{. . . . 1 7 . . .},
%w{. 8 . . . . . 3 6},
%w{. 7 . . . . . 4 .}]

row_validator = mock()
row_validator.expects(:valid?).returns(true)
RowValidator
.expects(:new)
.with(modified_grid)
.returns(row_validator)

SubgridValidator.new(grid).valid?
end
end

class SudokuValidatorTest < MiniTest::Unit::TestCase
def test_valid_complete
app = SudokuValidator.new('test/valid_complete.sudoku')
assert(app.valid?)
assert(app.complete?)
end

def test_valid_incomplete
app = SudokuValidator.new('test/valid_incomplete.sudoku')
assert(app.valid?)
refute(app.complete?)
end

def test_invalid_complete
app = SudokuValidator.new('test/invalid_complete.sudoku')
refute(app.valid?)
assert(app.complete?)
end

def test_invalid_incomplete
app = SudokuValidator.new('test/invalid_incomplete.sudoku')
refute(app.valid?)
refute(app.complete?)
end
end

File renamed without changes.
File renamed without changes.