Skip to content

Commit e7a5d64

Browse files
committed
Code cleanup, improving code coverage of SimpleSudokuSolver.Strategy namespace.
1 parent 2e43a09 commit e7a5d64

File tree

7 files changed

+99
-71
lines changed

7 files changed

+99
-71
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using NUnit.Framework;
2+
using SimpleSudokuSolver.Strategy;
3+
using System.Linq;
4+
5+
namespace SimpleSudokuSolver.Tests
6+
{
7+
[TestFixture]
8+
public class DefaultSolverTests
9+
{
10+
private readonly int[,] _sudoku = new int[,]
11+
{
12+
{ 0,0,0,0,0,0,0,0,3 },
13+
{ 3,0,0,0,0,0,0,0,9 },
14+
{ 0,0,0,0,9,3,1,4,2 },
15+
{ 0,0,0,8,6,9,3,2,4 },
16+
{ 0,0,0,3,1,2,8,5,7 },
17+
{ 2,3,8,4,7,5,9,6,1 },
18+
{ 0,0,4,7,5,8,2,3,6 },
19+
{ 5,6,7,2,3,1,4,9,8 },
20+
{ 8,2,3,9,4,6,7,1,5 }
21+
};
22+
23+
[Test]
24+
public void DefaultSolverWithDefaultStrategies_CanSolvePuzzle_Test()
25+
{
26+
var defaultSolver = new DefaultSolver();
27+
var sudokuPuzzle = defaultSolver.Solve(_sudoku);
28+
Assert.That(sudokuPuzzle.Cells[0, 0].Value, Is.EqualTo(4));
29+
}
30+
31+
[Test]
32+
public void DefaultSolverWithOnlyEliminationStrategies_CannotSolvePuzzle_Test()
33+
{
34+
var defaultSolver = new DefaultSolver(new BasicElimination());
35+
var sudokuPuzzle = defaultSolver.Solve(_sudoku);
36+
Assert.That(sudokuPuzzle.Cells[0, 0].Value, Is.EqualTo(0));
37+
Assert.That(sudokuPuzzle.Cells[0, 0].CanBe.Count, Is.EqualTo(5));
38+
}
39+
40+
[Test]
41+
public void SolveSingleStepForSolvedPuzle_ReturnsNull_Test()
42+
{
43+
var defaultSolver = new DefaultSolver();
44+
var sudokuPuzzle = defaultSolver.Solve(_sudoku);
45+
Assert.That(sudokuPuzzle.IsSolved);
46+
Assert.That(defaultSolver.SolveSingleStep(sudokuPuzzle), Is.Null);
47+
}
48+
}
49+
}

SimpleSudokuSolver.Tests/Strategy/LockedCandidatesTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ public void LockedCandidatesInRowTest()
5353
{ 0,0,0,0,0,0,0,0,0 }
5454
};
5555

56-
var solver = new DefaultSolver();
57-
var sudokuPuzzle = solver.Solve(sudoku);
56+
var sudokuPuzzle = new SudokuPuzzle(sudoku);
57+
SolveUsingStrategy(sudokuPuzzle, _strategy);
5858

5959
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 3].CanBe, 3);
6060
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 4].CanBe, 3);

SimpleSudokuSolver/DefaultSolver.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using SimpleSudokuSolver.Model;
22
using SimpleSudokuSolver.Strategy;
3-
using System.Collections.Generic;
43

54
namespace SimpleSudokuSolver
65
{
@@ -10,7 +9,36 @@ namespace SimpleSudokuSolver
109
public partial class DefaultSolver : ISudokuSolver
1110
{
1211
private SudokuPuzzle _sudokuPuzzleAfterFailedSolveSingleStep;
13-
private BasicElimination _basicElimination = new BasicElimination();
12+
private readonly BasicElimination _basicElimination = new BasicElimination();
13+
private readonly ISudokuSolverStrategy[] _strategies;
14+
15+
/// <summary>
16+
/// Constructor.
17+
/// </summary>
18+
/// <param name="strategies">
19+
/// Strategies used to try and solve the puzzle.
20+
/// If no strategies are provided, a default set of strategies is used.
21+
/// </param>
22+
public DefaultSolver(params ISudokuSolverStrategy[] strategies)
23+
{
24+
if (strategies.Length > 0)
25+
{
26+
_strategies = strategies;
27+
}
28+
else
29+
{
30+
_strategies = new ISudokuSolverStrategy[]
31+
{
32+
// Ordered from the simplest to the most complex
33+
new SingleInCells(),
34+
new HiddenSingle(),
35+
new NakedSingle(),
36+
new LockedCandidates(),
37+
new NakedPair(),
38+
new NakedTriple()
39+
};
40+
}
41+
}
1442

1543
/// <inheritdoc />
1644
public SudokuPuzzle Solve(int[,] sudoku)
@@ -29,6 +57,7 @@ public SudokuPuzzle Solve(int[,] sudoku)
2957
else
3058
{
3159
sudokuPuzzle.ApplySingleStepSolution(solution);
60+
_sudokuPuzzleAfterFailedSolveSingleStep = sudokuPuzzle;
3261
}
3362
}
3463

@@ -41,29 +70,17 @@ public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
4170
if (sudokuPuzzle.IsSolved())
4271
return null;
4372

44-
var solvingTechniques = new ISudokuSolverStrategy[]
45-
{
46-
// Ordered from the simples to the most complex
47-
new SingleInCells(),
48-
new HiddenSingle(),
49-
new NakedSingle(),
50-
new LockedCandidates(),
51-
new NakedPair(),
52-
new NakedTriple()
53-
};
54-
55-
foreach (var technique in solvingTechniques)
73+
foreach (var strategy in _strategies)
5674
{
5775
var elimination = _basicElimination.SolveSingleStep(sudokuPuzzle);
5876
if (elimination != null)
5977
return elimination;
6078

61-
var solution = technique.SolveSingleStep(sudokuPuzzle);
79+
var solution = strategy.SolveSingleStep(sudokuPuzzle);
6280
if (solution != null)
6381
return solution;
6482
}
6583

66-
_sudokuPuzzleAfterFailedSolveSingleStep = sudokuPuzzle;
6784
return null;
6885
}
6986
}

SimpleSudokuSolver/Model/Formatter.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,7 @@ internal static string PuzzleToString(SudokuPuzzle sudokuPuzzle)
8282
for (int j = 0; j < columnCount; j++)
8383
{
8484
var prefix = (j == 0 ? "" : " ");
85-
86-
if (sudokuPuzzle.Cells[i, j] != null)
87-
{
88-
sb.Append($"{prefix}{sudokuPuzzle.Cells[i, j].Value}");
89-
}
90-
else sb.Append($"{prefix} ");
85+
sb.Append($"{prefix}{sudokuPuzzle.Cells[i, j].Value}");
9186

9287
}
9388
sb.Append(Environment.NewLine);

SimpleSudokuSolver/Strategy/NakedSingle.cs

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
using SimpleSudokuSolver.Model;
2-
using System.Collections.Generic;
32
using System.Linq;
43

54
namespace SimpleSudokuSolver.Strategy
65
{
76
/// <summary>
87
/// Strategy runs after <see cref="BasicElimination"/> when we have already reduced number of candidates in each empty cell.
9-
/// For each row, column and block of the puzzle, strategy:
10-
/// - iterates through all the empty cells of that row, column and block
11-
/// - looks for a case where a cell can contain only one value
12-
/// (cell's list of candidates contains only one value)
8+
/// Strategy iterates through all the empty cells of the puzzle and looks for cells that can contain only one value
9+
/// (cell's list of candidates contains only one value).
1310
/// </summary>
1411
/// <remarks>
1512
/// See also:
@@ -22,33 +19,7 @@ public class NakedSingle : ISudokuSolverStrategy
2219

2320
public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
2421
{
25-
foreach (var row in sudokuPuzzle.Rows)
26-
{
27-
var rowSolution = GetNakedSingle(sudokuPuzzle, row.Cells);
28-
if (rowSolution != null)
29-
return rowSolution;
30-
}
31-
32-
foreach (var column in sudokuPuzzle.Columns)
33-
{
34-
var columnSolution = GetNakedSingle(sudokuPuzzle, column.Cells);
35-
if (columnSolution != null)
36-
return columnSolution;
37-
}
38-
39-
foreach (var block in sudokuPuzzle.Blocks)
40-
{
41-
var blockSolution = GetNakedSingle(sudokuPuzzle, block.Cells.OfType<Cell>());
42-
if (blockSolution != null)
43-
return blockSolution;
44-
}
45-
46-
return null;
47-
}
48-
49-
private SingleStepSolution GetNakedSingle(SudokuPuzzle sudokuPuzzle, IEnumerable<Cell> cells)
50-
{
51-
var cellsWithNoValue = cells.Where(x => !x.HasValue).ToArray();
22+
var cellsWithNoValue = sudokuPuzzle.Cells.OfType<Cell>().Where(x => !x.HasValue).ToArray();
5223

5324
// if cell can contain only one value, then it must contain that value
5425
foreach (var cell in cellsWithNoValue)

SimpleSudokuSolver/Strategy/NakedTriple.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,10 @@ public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
6060
for (int j = i + 1; j < nakedTripleCandidates.Length - 1; j++)
6161
{
6262
Cell second = nakedTripleCandidates[j];
63-
if (first == second)
64-
continue;
6563

6664
for (int k = j + 1; k < nakedTripleCandidates.Length; k++)
6765
{
6866
Cell third = nakedTripleCandidates[k];
69-
if (first == third || second == third)
70-
continue;
7167

7268
var distinctPotentialCellValuesInCandidates = GetDistinctPotentialCellValuesInCandidates(
7369
first.CanBe, second.CanBe, third.CanBe);
@@ -102,11 +98,8 @@ public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
10298
{
10399
foreach (var finalItem in finalItems)
104100
{
105-
if (cellWithNoValue.CanBe.Contains(finalItem))
106-
{
107-
var (RowIndex, ColumnIndex) = sudokuPuzzle.GetCellIndex(cellWithNoValue);
108-
eliminations.Add(new SingleStepSolution.Candidate(RowIndex, ColumnIndex, finalItem));
109-
}
101+
var (RowIndex, ColumnIndex) = sudokuPuzzle.GetCellIndex(cellWithNoValue);
102+
eliminations.Add(new SingleStepSolution.Candidate(RowIndex, ColumnIndex, finalItem));
110103
}
111104
}
112105
}

SimpleSudokuSolver/Strategy/SingleInCells.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,38 @@ namespace SimpleSudokuSolver.Strategy
99
/// </summary>
1010
public class SingleInCells : ISudokuSolverStrategy
1111
{
12-
public string StrategyName => "Single in Cells";
12+
public string StrategyName { get; private set; }
1313

1414
public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
1515
{
1616
foreach (var row in sudokuPuzzle.Rows)
1717
{
18-
var rowSolution = GetSingleInCells(sudokuPuzzle, row.Cells, "Single in Row");
18+
StrategyName = "Single in Row";
19+
var rowSolution = GetSingleInCells(sudokuPuzzle, row.Cells);
1920
if (rowSolution != null)
2021
return rowSolution;
2122
}
2223

2324
foreach (var column in sudokuPuzzle.Columns)
2425
{
25-
var columnSolution = GetSingleInCells(sudokuPuzzle, column.Cells, "Single in Column");
26+
StrategyName = "Single in Column";
27+
var columnSolution = GetSingleInCells(sudokuPuzzle, column.Cells);
2628
if (columnSolution != null)
2729
return columnSolution;
2830
}
2931

3032
foreach (var block in sudokuPuzzle.Blocks)
3133
{
32-
var blockSolution = GetSingleInCells(sudokuPuzzle, block.Cells.OfType<Cell>(), "Single in Block");
34+
StrategyName = "Single in Block";
35+
var blockSolution = GetSingleInCells(sudokuPuzzle, block.Cells.OfType<Cell>());
3336
if (blockSolution != null)
3437
return blockSolution;
3538
}
3639

3740
return null;
3841
}
3942

40-
private SingleStepSolution GetSingleInCells(SudokuPuzzle sudokuPuzzle, IEnumerable<Cell> cells, string strategyName)
43+
private SingleStepSolution GetSingleInCells(SudokuPuzzle sudokuPuzzle, IEnumerable<Cell> cells)
4144
{
4245
var cellsWithNoValue = cells.Where(x => !x.HasValue).ToArray();
4346

@@ -48,7 +51,7 @@ private SingleStepSolution GetSingleInCells(SudokuPuzzle sudokuPuzzle, IEnumerab
4851
var value = sudokuPuzzle.PossibleCellValues.Except(knownValues).Single();
4952

5053
var (RowIndex, ColumnIndex) = sudokuPuzzle.GetCellIndex(cellsWithNoValue[0]);
51-
return new SingleStepSolution(RowIndex, ColumnIndex, value, strategyName);
54+
return new SingleStepSolution(RowIndex, ColumnIndex, value, StrategyName);
5255
}
5356

5457
return null;

0 commit comments

Comments
 (0)