From ce935b9a1ac3cf9a7458f5d2a4f89c4e2109021b Mon Sep 17 00:00:00 2001 From: Alberto Venturini <aventurini@gmail.com> Date: Sat, 2 Nov 2019 15:17:12 +0200 Subject: [PATCH] Implemented backtracking algorithm --- spec/board_spec.cr | 4 ++-- spec/solver_spec.cr | 21 ++++++++++----------- src/board.cr | 2 +- src/solver.cr | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/spec/board_spec.cr b/spec/board_spec.cr index 3af7d03..d77f94f 100644 --- a/spec/board_spec.cr +++ b/spec/board_spec.cr @@ -64,11 +64,11 @@ describe Sudoku::Board do "...3" end - it "should not be solved if it's not solved" do + it "should not be complete if there are nulls" do board = Sudoku::Board.new(4, 2) (0..3).each { |i| board.put(i, i, i) } - board.solved?.should be_false + board.complete?.should be_false end diff --git a/spec/solver_spec.cr b/spec/solver_spec.cr index 1317523..12b8a19 100644 --- a/spec/solver_spec.cr +++ b/spec/solver_spec.cr @@ -17,17 +17,16 @@ describe Sudoku::Parser do board = Sudoku::Parser.parse(board_string, block_size: 3) Sudoku::Solver.solve(board) - true - # board.to_s.should eq \ - # "812753649\n" \ - # "943682175\n" \ - # "675491283\n" \ - # "154237896\n" \ - # "369845721\n" \ - # "287169534\n" \ - # "521974368\n" \ - # "438526917\n" \ - # "796318452" + board.to_s.should eq \ + "812753649\n" \ + "943682175\n" \ + "675491283\n" \ + "154237896\n" \ + "369845721\n" \ + "287169534\n" \ + "521974368\n" \ + "438526917\n" \ + "796318452" end diff --git a/src/board.cr b/src/board.cr index a211e56..3b6b022 100644 --- a/src/board.cr +++ b/src/board.cr @@ -71,7 +71,7 @@ module Sudoku }.join("\n") end - def solved? + def complete? @grid.flatten.none? { |i| i == nil } end diff --git a/src/solver.cr b/src/solver.cr index c5b6599..d3ea033 100644 --- a/src/solver.cr +++ b/src/solver.cr @@ -2,6 +2,50 @@ module Sudoku class Solver def self.solve(board) + solve_rec(board, find_next_nil(board, 0)) + end + + # Simple solver with backtracking + private def self.solve_rec(board, index) + if board.complete? + true + else + coords = index_to_coordinates(board, index) + x = coords[:x] + y = coords[:y] + + result = (1..board.size).any? { |v| + board.valid?(v, x, y) && begin + board.put(v, x, y) + solve_rec(board, find_next_nil(board, index)) + end + } + + if !result + board.put(nil, x, y) + end + + result + end + end + + private def self.find_next_nil(board, index) + max = (board.size ** 2) - 1 + + new_index = (index..max).find(-1) { |i| + coords = index_to_coordinates(board, i) + board.get(coords[:x], coords[:y]) == nil + } + + new_index + end + + private def self.coordinates_to_index(board, x, y) + y * board.size + x + end + + private def self.index_to_coordinates(board, index : Int32) + { x: index % board.size, y: index // board.size } end end