Skip to content

Commit 4e441d4

Browse files
committed
Implemented Backtracking (brute-force) solving strategy + tests.
1 parent 57959a7 commit 4e441d4

File tree

4 files changed

+129
-1
lines changed

4 files changed

+129
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ Out of the well known [Sudoku Solving Techniques](https://sudoku9x9.com/sudoku_s
2828
* Hidden Triple
2929
* Hidden Quad
3030
* X-Wing
31+
* Backtracking (brute-force)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using NUnit.Framework;
2+
using SimpleSudokuSolver.Strategy;
3+
4+
namespace SimpleSudokuSolver.Tests.Strategy
5+
{
6+
public class BacktrackingTests : BaseStrategyTest
7+
{
8+
private readonly ISudokuSolver solver = new DefaultSolver(new HiddenSingle(), new Backtracking());
9+
10+
[Test]
11+
public void BacktrackingTest1()
12+
{
13+
var sudoku = new[,]
14+
{
15+
// From: https://en.wikipedia.org/wiki/Sudoku_solving_algorithms#Backtracking
16+
{ 0,0,0,0,0,0,0,0,0 },
17+
{ 0,0,0,0,0,3,0,8,5 },
18+
{ 0,0,1,0,2,0,0,0,0 },
19+
{ 0,0,0,5,0,7,0,0,0 },
20+
{ 0,0,4,0,0,0,1,0,0 },
21+
{ 0,9,0,0,0,0,0,0,0 },
22+
{ 5,0,0,0,0,0,0,7,3 },
23+
{ 0,0,2,0,1,0,0,0,0 },
24+
{ 0,0,0,0,4,0,0,0,9 }
25+
};
26+
27+
var sudokuPuzzle = solver.Solve(sudoku);
28+
Assert.That(sudokuPuzzle.IsSolved(), Is.True);
29+
}
30+
}
31+
}

SimpleSudokuSolver/DefaultSolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public DefaultSolver(params ISudokuSolverStrategy[] strategies)
4141
new HiddenPair(),
4242
new HiddenTriple(),
4343
new HiddenQuad(),
44-
new XWing()
44+
new XWing(),
45+
new Backtracking()
4546
};
4647
}
4748
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using SimpleSudokuSolver.Model;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace SimpleSudokuSolver.Strategy
6+
{
7+
/// <summary>
8+
/// Implements brute-force search to find a solution for the puzzle.
9+
/// </remarks>
10+
/// <remarks>
11+
/// See also:
12+
/// - https://en.wikipedia.org/wiki/Sudoku_solving_algorithms#Backtracking
13+
/// </remarks>
14+
public class Backtracking : ISudokuSolverStrategy
15+
{
16+
public string StrategyName => "Backtracking (brute-force)";
17+
18+
public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
19+
{
20+
var sudokuPuzzleCopy = GetDeepCopy(sudokuPuzzle);
21+
bool isSolved = SolvePuzzle(sudokuPuzzleCopy);
22+
if (!isSolved)
23+
return null;
24+
25+
// find first cell with no value
26+
var cellWithNoValue = sudokuPuzzle.Cells.OfType<Cell>().First(x => !x.HasValue);
27+
28+
// return value of that cell
29+
return new SingleStepSolution(cellWithNoValue.RowIndex, cellWithNoValue.ColumnIndex,
30+
sudokuPuzzleCopy.Cells[cellWithNoValue.RowIndex, cellWithNoValue.ColumnIndex].Value, StrategyName);
31+
}
32+
33+
private SudokuPuzzle GetDeepCopy(SudokuPuzzle sudokuPuzzle)
34+
{
35+
var copy = new SudokuPuzzle(sudokuPuzzle.ToIntArray());
36+
foreach (var cell in sudokuPuzzle.Cells)
37+
{
38+
if (!cell.HasValue)
39+
{
40+
var cellInCopy = copy.Cells[cell.RowIndex, cell.ColumnIndex];
41+
cellInCopy.CanBe.Clear();
42+
cellInCopy.CanBe.AddRange(cell.CanBe);
43+
}
44+
}
45+
46+
return copy;
47+
}
48+
49+
private bool SolvePuzzle(SudokuPuzzle sudokuPuzzle)
50+
{
51+
if (sudokuPuzzle.IsSolved())
52+
return true;
53+
54+
var unsolvedCells = sudokuPuzzle.Cells.OfType<Cell>().Where(x => !x.HasValue).OrderBy(x => x.CanBe.Count).ToArray();
55+
56+
// for each cell
57+
foreach (var cell in unsolvedCells)
58+
{
59+
var row = sudokuPuzzle.Rows[cell.RowIndex];
60+
var column = sudokuPuzzle.Columns[cell.ColumnIndex];
61+
var blockIndex = sudokuPuzzle.GetBlockIndex(cell);
62+
var block = sudokuPuzzle.Blocks[blockIndex.RowIndex, blockIndex.ColumnIndex];
63+
64+
var potentialCellValues = new List<int>(cell.CanBe);
65+
66+
// for each potential value
67+
foreach (var value in potentialCellValues)
68+
{
69+
var usedInRow = row.Cells.Count(x => x.Value == value) > 0;
70+
var usedInColumn = column.Cells.Count(x => x.Value == value) > 0;
71+
var usedInBlock = block.Cells.OfType<Cell>().Count(x => x.Value == value) > 0;
72+
73+
// if potential value already used in row, column or block, skip it
74+
if (usedInRow || usedInColumn || usedInBlock)
75+
continue;
76+
77+
// try to recursively solve the puzzle
78+
cell.Value = value;
79+
if (SolvePuzzle(sudokuPuzzle))
80+
{
81+
return true;
82+
}
83+
// if puzzle cannot be solve, revert the value
84+
else
85+
{
86+
cell.Value = 0;
87+
cell.CanBe.AddRange(potentialCellValues);
88+
}
89+
}
90+
return false;
91+
}
92+
return false;
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)