diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..5f16476 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format progress diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..b0c7645 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'rspec' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..7359464 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,18 @@ +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.2.4) + 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) + +PLATFORMS + ruby + +DEPENDENCIES + rspec diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb new file mode 100644 index 0000000..c292889 --- /dev/null +++ b/lib/sudoku_validator.rb @@ -0,0 +1,77 @@ +require 'Matrix' + +class SudokuValidator + def initialize(file=nil) + return if file.nil? + + puzzle = [] + open(file).each do |line| + row = line.gsub(/[|\-+]/, "") + unless row.strip.empty? + puzzle.push(row.split(' ').map{|x| x == '.' ? nil : x.to_i}) + end + end + + @puzzle = Matrix.rows(puzzle) + end + + def valid? + return false if @puzzle.nil? + return validate(@puzzle) + end + + def complete? + @puzzle.row_vectors.each do |row| + return false if row.include? nil + end + true + end + + def validate(puzzle) + valid_puzzle?(puzzle) && valid_rows?(puzzle) && valid_columns?(puzzle) && valid_submatricies?(puzzle) + end + + def valid_puzzle?(puzzle) + puzzle.square? && (puzzle.row_count % 3) == 0 + end + + def valid_rows?(puzzle) + puzzle.row_vectors.each do |row| + return false unless grouping_valid?(row) + end + + true + end + + def valid_columns?(puzzle) + puzzle.column_vectors.each do |column| + return false unless grouping_valid?(column) + end + + true + end + + def valid_submatricies?(puzzle) + (puzzle.row_count / 3).times do |row| + (puzzle.column_count / 3).times do |column| + subpuzzle = puzzle.minor(3*row..(3*row)+2, 3*column..(3*column)+2) + flattened = [] + subpuzzle.row_vectors.each do |subrow| + flattened += subrow.to_a + end + return false unless grouping_valid?(flattened) + end + end + + true + end + + def grouping_valid?(set) + values = Hash.new(0) + set.each do |value| + values[value] += 1 unless value == nil + end + + values.find{|k, v| v > 1 } == nil + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..dbc4f1a --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,17 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# Require this file using `require "spec_helper"` to ensure that it is only +# loaded once. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run :focus + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = 'random' +end diff --git a/spec/sudoku_validator_spec.rb b/spec/sudoku_validator_spec.rb new file mode 100644 index 0000000..f495a0a --- /dev/null +++ b/spec/sudoku_validator_spec.rb @@ -0,0 +1,49 @@ +require 'sudoku_validator' + +describe SudokuValidator do + before (:each) do + @validator = SudokuValidator.new + end + + it "has no duplicates in a row" do + validPuzzle = Matrix[[1,2,3],[nil,nil,nil],[nil,nil,nil]] + @validator.validate(validPuzzle).should be_true + + invalidPuzzle = Matrix[[1,1,2],[nil,nil,nil],[nil,nil,nil]] + @validator.validate(invalidPuzzle).should be_false + end + + it "has no duplicates in a column" do + validPuzzle = Matrix[[1,nil,nil],[2,nil,nil],[3,nil,nil]] + @validator.validate(validPuzzle).should be_true + + invalidPuzzle = Matrix[[1,nil,nil], [1,nil,nil], [2,nil,nil]] + @validator.validate(invalidPuzzle).should be_false + end + + it "has no duplicates in a sub-grid" do + validPuzzle = Matrix[[1,2,3],[4,5,6],[7,8,9]] + @validator.validate(validPuzzle).should be_true + + invalidPuzzle = Matrix[[1,2,3],[4,5,6],[7,8,1]] + @validator.validate(invalidPuzzle).should be_false + end + + it "validates files" do + vc = SudokuValidator.new("valid_complete.sudoku") + vc.valid?.should be_true + vc.complete?.should be_true + + vi = SudokuValidator.new("valid_incomplete.sudoku") + vi.valid?.should be_true + vi.complete?.should be_false + + ic = SudokuValidator.new("invalid_complete.sudoku") + ic.valid?.should be_false + ic.complete?.should be_true + + ii = SudokuValidator.new("invalid_incomplete.sudoku") + ii.valid?.should be_false + ii.complete?.should be_false + end +end diff --git a/sudoku-validator b/sudoku-validator new file mode 100755 index 0000000..d379acd --- /dev/null +++ b/sudoku-validator @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require_relative 'lib/sudoku_validator' + +validator = SudokuValidator.new(ARGV[0]) +puts "This sudoku is #{!validator.valid? ? "in" : ""}valid#{validator.valid? && !validator.complete? ? ", but incomplete" : ""}."