From aba22b3165fd9b283a3ff5d842f02bdad0899cf3 Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 14:03:10 +0100 Subject: [PATCH 1/6] Move example sudoku files to test dir --- invalid_complete.sudoku => test/invalid_complete.sudoku | 0 invalid_incomplete.sudoku => test/invalid_incomplete.sudoku | 0 valid_complete.sudoku => test/valid_complete.sudoku | 0 valid_incomplete.sudoku => test/valid_incomplete.sudoku | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename invalid_complete.sudoku => test/invalid_complete.sudoku (100%) rename invalid_incomplete.sudoku => test/invalid_incomplete.sudoku (100%) rename valid_complete.sudoku => test/valid_complete.sudoku (100%) rename valid_incomplete.sudoku => test/valid_incomplete.sudoku (100%) diff --git a/invalid_complete.sudoku b/test/invalid_complete.sudoku similarity index 100% rename from invalid_complete.sudoku rename to test/invalid_complete.sudoku diff --git a/invalid_incomplete.sudoku b/test/invalid_incomplete.sudoku similarity index 100% rename from invalid_incomplete.sudoku rename to test/invalid_incomplete.sudoku diff --git a/valid_complete.sudoku b/test/valid_complete.sudoku similarity index 100% rename from valid_complete.sudoku rename to test/valid_complete.sudoku diff --git a/valid_incomplete.sudoku b/test/valid_incomplete.sudoku similarity index 100% rename from valid_incomplete.sudoku rename to test/valid_incomplete.sudoku From e587f7cb66d15fd89a72b13b1a7b75f94303ea2d Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 14:08:08 +0100 Subject: [PATCH 2/6] Sudoku file parser --- lib/sudoku_validator.rb | 19 +++++++++++++++++++ test/sudoku_validator_test.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 lib/sudoku_validator.rb create mode 100644 test/sudoku_validator_test.rb diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb new file mode 100644 index 0000000..c396194 --- /dev/null +++ b/lib/sudoku_validator.rb @@ -0,0 +1,19 @@ +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 + end +end diff --git a/test/sudoku_validator_test.rb b/test/sudoku_validator_test.rb new file mode 100644 index 0000000..26570c6 --- /dev/null +++ b/test/sudoku_validator_test.rb @@ -0,0 +1,29 @@ +require 'minitest/autorun' +require 'minitest/pride' + +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 = + [['8', '5', '.', '.', '.', '2', '4', '.', '.'], + ['7', '2', '.', '.', '.', '.', '.', '.', '9'], + ['.', '.', '4', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '1', '.', '7', '.', '.', '2'], + ['3', '.', '5', '.', '.', '.', '9', '.', '.'], + ['.', '4', '.', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '8', '.', '.', '7', '.'], + ['.', '1', '7', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '3', '6', '.', '4', '.']] + + parser = SudokuFileParser.new('test/valid_incomplete.sudoku') + assert_equal(expected, parser.grid) + end +end From 4b7a2225907eb2b27452edce56039c60888b906c Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 14:16:20 +0100 Subject: [PATCH 3/6] Sudoku row validator --- lib/sudoku_validator.rb | 18 ++++++++++++++++++ test/sudoku_validator_test.rb | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb index c396194..f519177 100644 --- a/lib/sudoku_validator.rb +++ b/lib/sudoku_validator.rb @@ -17,3 +17,21 @@ def gridify(stripped) stripped.chars.each_slice(9).to_a 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 diff --git a/test/sudoku_validator_test.rb b/test/sudoku_validator_test.rb index 26570c6..b767efb 100644 --- a/test/sudoku_validator_test.rb +++ b/test/sudoku_validator_test.rb @@ -27,3 +27,27 @@ def test_parses_as_strings assert_equal(expected, parser.grid) end end + +class RowValidatorTest < MiniTest::Unit::TestCase + def test_valid_when_unique_in_row + grid = [['1', '2', '3'], ['3', '2', '1']] + + validator = RowValidator.new(grid) + + assert(validator.valid?) + end + + def test_invalid_when_duplicates_in_row + grid = [['1', '2', '3'], ['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 From e76c96e69503d2196d24bbd51042d5e5eaa880c4 Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 14:40:53 +0100 Subject: [PATCH 4/6] Sudoku column validator --- Gemfile | 4 ++++ Gemfile.lock | 12 ++++++++++++ lib/sudoku_validator.rb | 10 ++++++++++ test/sudoku_validator_test.rb | 19 +++++++++++++++++-- 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d34982f --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +# A sample Gemfile +source "https://rubygems.org" + +gem "mocha" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..2ef099e --- /dev/null +++ b/Gemfile.lock @@ -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 diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb index f519177..2cdc628 100644 --- a/lib/sudoku_validator.rb +++ b/lib/sudoku_validator.rb @@ -35,3 +35,13 @@ def strip_dots(grid) end end end + +class ColumnValidator + def initialize(grid) + @validator = RowValidator.new(grid.transpose) + end + + def valid? + @validator.valid? + end +end diff --git a/test/sudoku_validator_test.rb b/test/sudoku_validator_test.rb index b767efb..f9e7430 100644 --- a/test/sudoku_validator_test.rb +++ b/test/sudoku_validator_test.rb @@ -1,5 +1,6 @@ require 'minitest/autorun' require 'minitest/pride' +require 'mocha/mini_test' require_relative '../lib/sudoku_validator' @@ -30,7 +31,7 @@ def test_parses_as_strings class RowValidatorTest < MiniTest::Unit::TestCase def test_valid_when_unique_in_row - grid = [['1', '2', '3'], ['3', '2', '1']] + grid = [%w{1 2 3}, %w{3 2 1}] validator = RowValidator.new(grid) @@ -38,7 +39,7 @@ def test_valid_when_unique_in_row end def test_invalid_when_duplicates_in_row - grid = [['1', '2', '3'], ['3', '2', '3']] + grid = [%w{1 2 3}, %w{3 2 3}] validator = RowValidator.new(grid) @@ -51,3 +52,17 @@ def test_ignore_dots 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 From 9bd5ba82937a0385fdf408b4cc33f55ef03d6b76 Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 16:35:52 +0100 Subject: [PATCH 5/6] Sudoku subgrid validator --- lib/sudoku_validator.rb | 26 +++++++++++++++++ test/sudoku_validator_test.rb | 54 +++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb index 2cdc628..c3db25e 100644 --- a/lib/sudoku_validator.rb +++ b/lib/sudoku_validator.rb @@ -45,3 +45,29 @@ 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 +end diff --git a/test/sudoku_validator_test.rb b/test/sudoku_validator_test.rb index f9e7430..7d1c687 100644 --- a/test/sudoku_validator_test.rb +++ b/test/sudoku_validator_test.rb @@ -14,15 +14,15 @@ def test_reads_file_into_9x9_2d_array def test_parses_as_strings expected = - [['8', '5', '.', '.', '.', '2', '4', '.', '.'], - ['7', '2', '.', '.', '.', '.', '.', '.', '9'], - ['.', '.', '4', '.', '.', '.', '.', '.', '.'], - ['.', '.', '.', '1', '.', '7', '.', '.', '2'], - ['3', '.', '5', '.', '.', '.', '9', '.', '.'], - ['.', '4', '.', '.', '.', '.', '.', '.', '.'], - ['.', '.', '.', '.', '8', '.', '.', '7', '.'], - ['.', '1', '7', '.', '.', '.', '.', '.', '.'], - ['.', '.', '.', '.', '3', '6', '.', '4', '.']] + [%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) @@ -66,3 +66,39 @@ def test_is_implemented_in_terms_of_row_validator_for_transposed_grid 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 + From 6918794c635bef44eedeae79d9dbed3bc5d77e07 Mon Sep 17 00:00:00 2001 From: szymzet Date: Wed, 19 Mar 2014 16:56:47 +0100 Subject: [PATCH 6/6] Final executable script with validator --- lib/sudoku_validator.rb | 20 ++++++++++++++++++++ sudoku-validator | 12 ++++++++++++ test/sudoku_validator_test.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100755 sudoku-validator diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb index c3db25e..7a3305a 100644 --- a/lib/sudoku_validator.rb +++ b/lib/sudoku_validator.rb @@ -18,6 +18,7 @@ def gridify(stripped) end end + class RowValidator def initialize(grid) @stripped_grid = strip_dots(grid) @@ -36,6 +37,7 @@ def strip_dots(grid) end end + class ColumnValidator def initialize(grid) @validator = RowValidator.new(grid.transpose) @@ -46,6 +48,7 @@ def valid? end end + class SubgridValidator def initialize(grid) modified_grid = subgrids_to_rows(grid) @@ -71,3 +74,20 @@ def row_of_subgrids(rows) .map { |i, e| e.flatten } # form a row from subgrid elements end end + + +class SudokuValidator + def initialize(filename) + @grid = SudokuFileParser.new(filename).grid + @complete = !@grid.flatten.any? { |e| e == '.' } + end + + def valid? + validators = [RowValidator, ColumnValidator, SubgridValidator] + validators.all? { |v| v.new(@grid).valid? } + end + + def complete? + @complete + end +end diff --git a/sudoku-validator b/sudoku-validator new file mode 100755 index 0000000..df1916c --- /dev/null +++ b/sudoku-validator @@ -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 diff --git a/test/sudoku_validator_test.rb b/test/sudoku_validator_test.rb index 7d1c687..a4b33df 100644 --- a/test/sudoku_validator_test.rb +++ b/test/sudoku_validator_test.rb @@ -102,3 +102,29 @@ def test_is_implemented_in_terms_of_row_validator_for_modified_grid 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 +