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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/tmp
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--color
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

gem 'rspec'
gem 'guard-rspec'
48 changes: 48 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
GEM
specs:
celluloid (0.15.2)
timers (~> 1.1.0)
coderay (1.0.9)
diff-lcs (1.2.4)
ffi (1.9.0)
formatador (0.2.4)
guard (2.0.3)
formatador (>= 0.2.4)
listen (~> 2.0)
lumberjack (~> 1.0)
pry (>= 0.9.12)
thor (>= 0.18.1)
guard-rspec (3.1.0)
guard (>= 1.8)
rspec (~> 2.13)
listen (2.0.1)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.4)
method_source (0.8.2)
pry (0.9.12.2)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
rb-fsevent (0.9.3)
rb-inotify (0.9.2)
ffi (>= 0.5.0)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.5)
rspec-expectations (2.14.3)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.3)
slop (3.4.6)
thor (0.18.1)
timers (1.1.0)

PLATFORMS
ruby

DEPENDENCIES
guard-rspec
rspec
7 changes: 7 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

guard :rspec, cli: "--tag ~@external" do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
watch(%r{^spec/support/.+\.rb$}) { "spec" }
end
30 changes: 30 additions & 0 deletions lib/sudoku_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

class SudokuReader

def self.reader_from_file(file_path)
reader = SudokuReader.new
reader.read(file_path)
reader
end

attr_reader :grid

def initialize
@grid = []
end

def read(file_path)
open(file_path).each do |line|
parsed_line = parse_line line
@grid << parsed_line if parsed_line
end
end

def parse_line(line)
line.split(/[^\d\.]+/) unless separator_line? line
end

def separator_line?(line)
line =~/^-+/
end
end
119 changes: 119 additions & 0 deletions lib/sudoku_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
class SudokuValidator

attr_reader :grid
attr_reader :errors

def initialize(file_path)
@grid = SudokuReader.reader_from_file(file_path).grid
@errors = {
invalid: {
column: [],
row: [],
subgrid: []
},
incomplete: {
column: [],
row: [],
subgrid: []
}
}
end

def valid_array?(array)
numbers_only(array).uniq.length == numbers_only(array).length
end

def complete_array?(array)
numbers_only(array).length == 9
end

def numbers_only(array)
array.reject { |el| el == "." }
end

def array_is?(validation_type, array)
if validation_type == :invalid
not valid_array? array
else
not complete_array? array
end
end

def row(i)
grid[i-1]
end

def column(i)
grid.map { |row| row[i-1] }
end

# subgrid is laid out as below
# 1-based 0-based
# 1 2 3 0 1 2 <- grid_index for column
# 1 - 3 4 - 6 7 - 9 0 - 2 3 - 5 6 - 8
# 1 . . . | . . . | . . . 0 . . . | . . . | . . .
# 1 | . 1 . | . 2 . | . 3 . 0 | . 0 . | . 1 . | . 2 .
# 3 . . . | . . . | . . . 2 . . . | . . . | . . .
# +-----+-------+-----+ +-----+-------+-----+
# 4 . . . | . . . | . . . 3 . . . | . . . | . . .
# 2 | . 4 . | . 5 . | . 6 . 1 | . 3 . | . 4 . | . 5 .
# 6 . . . | . . . | . . . 5 . . . | . . . | . . .
# +-----+-------+-----+ +-----+-------+-----+
# 7 . . . | . . . | . . . 6 . . . | . . . | . . .
# 3 | . 7 . | . 8 . | . 9 . 2 | . 6 . | . 7 . | . 8 .
# 9 . . . | . . . | . . . 8 . . . | . . . | . . .
#
# ^
# grid_index for row
#
def subgrid(i)
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a lot of complexity in this method. Extracting functionality into small, well-named private methods would make this a lot easier to read.

Copy link
Author

Choose a reason for hiding this comment

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

improved... take a look at the new implementation. Thanks for pointing this out.

grid[row_starts_for_grid(i), 3].map do |row|
row[column_starts_for_grid(i), 3]
end.flatten
end

def row_starts_for_grid(i)
zero_based(i) / 3 * 3
end

def column_starts_for_grid(i)
zero_based(i) % 3 * 3
end

def zero_based(i)
i - 1
end

def validate!
reset_errors!
[:invalid, :incomplete].each do |error_type|
[:row, :column, :subgrid].each do |array_type|
(1..9).each do |i|
errors[error_type][array_type] << i if array_is? error_type, send(array_type, i)
end
end
end
end

def valid?
[:row, :column, :subgrid].all? { |array_type| errors[:invalid][array_type].none? }
end

def complete?
[:row, :column, :subgrid].all? { |array_type| errors[:incomplete][array_type].none? }
end

def reset_errors!
[:invalid, :incomplete].each do |error_type|
[:row, :column, :subgrid].each do |array_type|
errors[error_type][array_type].clear
end
end
end

def error_messages(type)
errors[type].map do |key, vals|
"#{type} #{key}s: #{vals.join(", ")}" if vals.any?
end.compact.sort
end
end
11 changes: 11 additions & 0 deletions spec/fixtures/invalid_complete.sudoku
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
8 5 9 |6 1 2 |4 3 7
7 2 3 |8 5 4 |1 6 9
1 6 4 |3 7 9 |5 2 8
------+------+------
9 8 6 |1 4 7 |3 5 2
3 7 5 |8 6 2 |9 1 4
2 4 1 |5 9 3 |7 8 6
------+------+------
4 3 2 |9 8 1 |6 7 5
6 1 7 |4 2 5 |8 9 3
5 9 8 |7 3 6 |2 4 1
11 changes: 11 additions & 0 deletions spec/fixtures/invalid_incomplete.sudoku
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
8 5 . |. . 2 |4 . .
7 2 . |. 8 . |. . 9
. . 4 |. . . |. . .
------+------+------
. . . |1 . 7 |. . 2
3 . 5 |. . . |9 . .
. 4 . |. . . |. . .
------+------+------
. 5 . |. 8 . |. 7 .
. 1 7 |. . . |. . .
. . . |. 3 6 |. 4 .
11 changes: 11 additions & 0 deletions spec/fixtures/valid_complete.sudoku
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
8 5 9 |6 1 2 |4 3 7
7 2 3 |8 5 4 |1 6 9
1 6 4 |3 7 9 |5 2 8
------+------+------
9 8 6 |1 4 7 |3 5 2
3 7 5 |2 6 8 |9 1 4
2 4 1 |5 9 3 |7 8 6
------+------+------
4 3 2 |9 8 1 |6 7 5
6 1 7 |4 2 5 |8 9 3
5 9 8 |7 3 6 |2 4 1
11 changes: 11 additions & 0 deletions spec/fixtures/valid_incomplete.sudoku
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
8 5 . |. . 2 |4 . .
7 2 . |. . . |. . 9
. . 4 |. . . |. . .
------+------+------
. . . |1 . 7 |. . 2
3 . 5 |. . . |9 . .
. 4 . |. . . |. . .
------+------+------
. . . |. 8 . |. 7 .
. 1 7 |. . . |. . .
. . . |. 3 6 |. 4 .
61 changes: 61 additions & 0 deletions spec/lib/sudoku_reader_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require 'spec_helper'

describe SudokuReader do

let(:reader) { SudokuReader.new }

describe '#parse_line' do
it 'returns an array' do
parsed_line = reader.parse_line "8 5 9 |6 1 2 |4 3 7 "

expect(parsed_line).to eq ["8", "5", "9", "6", "1", "2", "4", "3", "7"]

parsed_line = reader.parse_line ". . . |. 8 . |. 7 . "

expect(parsed_line).to eq [".", ".", ".", ".", "8", ".", ".", "7", "."]

parsed_line = reader.parse_line "------+------+------"

expect(parsed_line).to be_nil
end
end

describe '#separator_line?' do
it 'returns true if the line is -----' do
line = "------+------+------"

expect(reader.separator_line?(line)).to be_true

line = "8 5 9 |6 1 2 |4 3 7 "

expect(reader.separator_line?(line)).to be_false

line = ". . . |. 8 . |. 7 . "

expect(reader.separator_line?(line)).to be_false
end
end

describe '#read' do
it 'reads and stores the file into an array' do
reader.read fixture_path('valid_incomplete.sudoku')

expect(reader.grid.length).to eq 9 # rows
reader.grid.each do |row|
expect(row.length).to eq 9 # columns
end
expect(reader.grid[0][4]).to eq "."
expect(reader.grid[2][2]).to eq "4"
end
end

describe '#reader_from_file' do
it 'creates an instance of reader with a grid generated from file' do
reader = SudokuReader.reader_from_file(fixture_path('valid_incomplete.sudoku'))

expect(reader.grid.length).to eq 9
expect(reader.grid[0][4]).to eq "."
expect(reader.grid[2][2]).to eq "4"
end
end
end
Loading