-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented HiddenPair, HiddenTriple and HiddenQuad solving strategie…
…s + tests.
- Loading branch information
Showing
8 changed files
with
476 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
using NUnit.Framework; | ||
using SimpleSudokuSolver.Model; | ||
using SimpleSudokuSolver.Strategy; | ||
|
||
namespace SimpleSudokuSolver.Tests.Strategy | ||
{ | ||
public class HiddenPairTests : BaseStrategyTest | ||
{ | ||
private readonly ISudokuSolverStrategy _strategy = new HiddenPair(); | ||
|
||
[Test] | ||
public void HiddenPairTest1() | ||
{ | ||
var sudoku = new int[,] | ||
{ | ||
// From: http://www.sudokuwiki.org/Hidden_Candidates | ||
{ 0,0,0,0,0,0,0,0,0 }, | ||
{ 9,0,4,6,0,7,0,0,0 }, | ||
{ 0,7,6,8,0,4,1,0,0 }, | ||
{ 3,0,9,7,0,1,0,8,0 }, | ||
{ 0,0,8,0,0,0,3,0,0 }, | ||
{ 0,5,0,3,0,8,7,0,2 }, | ||
{ 0,0,7,5,0,2,6,1,0 }, | ||
{ 0,0,0,4,0,3,2,0,8 }, | ||
{ 0,0,0,0,0,0,0,0,0 } | ||
}; | ||
|
||
var sudokuPuzzle = new SudokuPuzzle(sudoku); | ||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 7].CanBe, 2); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 7].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 7].CanBe, 4); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 7].CanBe, 5); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 7].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 4); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 5); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 9); | ||
} | ||
|
||
[Test] | ||
public void HiddenPairTest2() | ||
{ | ||
var sudoku = new int[,] | ||
{ | ||
// From: http://www.sudokuwiki.org/Hidden_Candidates | ||
{ 7,2,0,4,0,8,0,3,0 }, | ||
{ 0,8,0,0,0,0,0,4,7 }, | ||
{ 4,0,1,0,7,6,8,0,2 }, | ||
{ 8,1,0,7,3,9,0,0,0 }, | ||
{ 0,0,0,8,5,1,0,0,0 }, | ||
{ 0,0,0,2,6,4,0,8,0 }, | ||
{ 2,0,9,6,8,0,4,1,3 }, | ||
{ 3,4,0,0,0,0,0,0,8 }, | ||
{ 1,6,8,9,4,3,2,7,5 } | ||
}; | ||
|
||
var sudokuPuzzle = new SudokuPuzzle(sudoku); | ||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 2].CanBe, 5); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 2].CanBe, 6); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 1].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 2].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 2].CanBe, 6); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 2].CanBe, 7); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 6].CanBe, 6); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[4, 6].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 6].CanBe, 1); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 6].CanBe, 5); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 6].CanBe, 9); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using NUnit.Framework; | ||
using SimpleSudokuSolver.Model; | ||
using SimpleSudokuSolver.Strategy; | ||
|
||
namespace SimpleSudokuSolver.Tests.Strategy | ||
{ | ||
public class HiddenQuadTests : BaseStrategyTest | ||
{ | ||
private readonly ISudokuSolverStrategy _strategy = new HiddenQuad(); | ||
|
||
[Test] | ||
public void HiddenQuadTest1() | ||
{ | ||
var sudoku = new int[,] | ||
{ | ||
// From: http://www.sudokuwiki.org/Hidden_Candidates | ||
{ 6,5,0,0,8,7,0,2,4 }, | ||
{ 0,0,0,6,4,9,0,5,0 }, | ||
{ 0,4,0,0,2,5,0,0,0 }, | ||
{ 5,7,0,4,3,8,0,6,1 }, | ||
{ 0,0,0,5,0,1,0,0,0 }, | ||
{ 3,1,0,9,0,2,0,8,5 }, | ||
{ 0,0,0,8,9,0,0,1,0 }, | ||
{ 0,0,0,2,1,3,0,0,0 }, | ||
{ 1,3,0,7,5,0,0,9,8 } | ||
}; | ||
|
||
var sudokuPuzzle = new SudokuPuzzle(sudoku); | ||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
// manual adaptation of CanBe so HiddenQuad can be applied | ||
sudokuPuzzle.Cells[2, 6].CanBe.Remove(9); | ||
sudokuPuzzle.Cells[4, 6].CanBe.Remove(2); | ||
sudokuPuzzle.Cells[4, 6].CanBe.Remove(9); | ||
sudokuPuzzle.Cells[6, 6].CanBe.Remove(2); | ||
sudokuPuzzle.Cells[6, 6].CanBe.Remove(4); | ||
sudokuPuzzle.Cells[8, 6].CanBe.Remove(4); | ||
|
||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[6, 6].CanBe, 6); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[7, 6].CanBe, 6); | ||
} | ||
|
||
[Test] | ||
public void HiddenQuadTest2() | ||
{ | ||
var sudoku = new int[,] | ||
{ | ||
// From: http://www.sudokuwiki.org/Hidden_Candidates | ||
{ 9,0,1,5,0,0,0,4,6 }, | ||
{ 4,2,5,0,9,0,0,8,1 }, | ||
{ 8,6,0,0,1,0,0,2,0 }, | ||
{ 5,0,2,0,0,0,0,0,0 }, | ||
{ 0,1,9,0,0,0,4,6,0 }, | ||
{ 6,0,0,0,0,0,0,0,2 }, | ||
{ 1,9,6,0,4,0,2,5,3 }, | ||
{ 2,0,0,0,6,0,8,1,7 }, | ||
{ 0,0,0,0,0,1,6,9,4 } | ||
}; | ||
|
||
var sudokuPuzzle = new SudokuPuzzle(sudoku); | ||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 3].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 3].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 3].CanBe, 8); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 5].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 5].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[3, 5].CanBe, 8); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 3].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 3].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 3].CanBe, 8); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 5].CanBe, 3); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 5].CanBe, 5); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 5].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 5].CanBe, 8); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using NUnit.Framework; | ||
using SimpleSudokuSolver.Model; | ||
using SimpleSudokuSolver.Strategy; | ||
|
||
namespace SimpleSudokuSolver.Tests.Strategy | ||
{ | ||
public class HiddenTripleTests : BaseStrategyTest | ||
{ | ||
private readonly ISudokuSolverStrategy _strategy = new HiddenTriple(); | ||
|
||
[Test] | ||
public void HiddenTripleTest1() | ||
{ | ||
var sudoku = new int[,] | ||
{ | ||
// From: http://www.sudokuwiki.org/Hidden_Candidates | ||
{ 0,0,0,0,0,1,0,3,0 }, | ||
{ 2,3,1,0,9,0,0,0,0 }, | ||
{ 0,6,5,0,0,3,1,0,0 }, | ||
{ 6,7,8,9,2,4,3,0,0 }, | ||
{ 1,0,3,0,5,0,0,0,6 }, | ||
{ 0,0,0,1,3,6,7,0,0 }, | ||
{ 0,0,9,3,6,0,5,7,0 }, | ||
{ 0,0,6,0,1,9,8,4,3 }, | ||
{ 3,0,0,0,0,0,0,0,0 } | ||
}; | ||
|
||
var sudokuPuzzle = new SudokuPuzzle(sudoku); | ||
SolveUsingStrategy(sudokuPuzzle, _strategy); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 3].CanBe, 4); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 3].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 3].CanBe, 8); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 6].CanBe, 4); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 6].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 4); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 7); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 8); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 8].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 8].CanBe, 5); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[2, 8].CanBe, 2); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[2, 8].CanBe, 9); | ||
|
||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 8].CanBe, 2); | ||
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[5, 8].CanBe, 9); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
using SimpleSudokuSolver.Model; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace SimpleSudokuSolver.Strategy | ||
{ | ||
/// <summary> | ||
/// Strategy looks for two cells in the same row / column / block that have two candidate values that cannot | ||
/// be in any other cell of the same row / column / block. | ||
/// If such two cells are found, all other candidate values from those two cells can be removed. | ||
/// </summary> | ||
/// <remarks> | ||
/// See also: | ||
/// - https://sudoku9x9.com/hidden_pair.html | ||
/// - http://www.sudokuwiki.org/Hidden_Candidates | ||
/// </remarks> | ||
public class HiddenPair : HiddenPairTripleQuadBase, ISudokuSolverStrategy | ||
{ | ||
public string StrategyName => "Hidden Pair"; | ||
|
||
public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle) | ||
{ | ||
return GetSingleStepSolution(sudokuPuzzle, StrategyName); | ||
} | ||
|
||
protected override IEnumerable<SingleStepSolution.Candidate> GetHiddenEliminations( | ||
IEnumerable<Cell> cells, SudokuPuzzle sudokuPuzzle) | ||
{ | ||
var cellsWithNoValue = cells.Where(x => !x.HasValue).ToArray(); | ||
var hiddenCandidates = GetHiddenCandidates(cellsWithNoValue, sudokuPuzzle, 2); | ||
var eliminations = new List<SingleStepSolution.Candidate>(); | ||
|
||
for (int i = 1; i <= sudokuPuzzle.NumberOfRowsOrColumnsInPuzzle - 1; i++) | ||
{ | ||
for (int j = i + 1; j <= sudokuPuzzle.NumberOfRowsOrColumnsInPuzzle; j++) | ||
{ | ||
if (hiddenCandidates.ContainsKey(i) && | ||
hiddenCandidates.ContainsKey(j) && | ||
hiddenCandidates[i].SequenceEqual(hiddenCandidates[j])) | ||
{ | ||
eliminations.AddRange(GetEliminations(hiddenCandidates[i][0], sudokuPuzzle, i, j)); | ||
eliminations.AddRange(GetEliminations(hiddenCandidates[i][1], sudokuPuzzle, i, j)); | ||
} | ||
} | ||
} | ||
|
||
return eliminations; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using SimpleSudokuSolver.Model; | ||
|
||
namespace SimpleSudokuSolver.Strategy | ||
{ | ||
public abstract class HiddenPairTripleQuadBase | ||
{ | ||
protected abstract IEnumerable<SingleStepSolution.Candidate> GetHiddenEliminations( | ||
IEnumerable<Cell> cells, SudokuPuzzle sudokuPuzzle); | ||
|
||
protected SingleStepSolution GetSingleStepSolution(SudokuPuzzle sudokuPuzzle, string strategyName) | ||
{ | ||
var eliminations = new List<SingleStepSolution.Candidate>(); | ||
|
||
foreach (var row in sudokuPuzzle.Rows) | ||
{ | ||
eliminations.AddRange(GetHiddenEliminations(row.Cells, sudokuPuzzle)); | ||
} | ||
|
||
foreach (var column in sudokuPuzzle.Columns) | ||
{ | ||
eliminations.AddRange(GetHiddenEliminations(column.Cells, sudokuPuzzle)); | ||
} | ||
|
||
foreach (var block in sudokuPuzzle.Blocks) | ||
{ | ||
eliminations.AddRange(GetHiddenEliminations(block.Cells.OfType<Cell>(), sudokuPuzzle)); | ||
} | ||
|
||
return eliminations.Count > 0 ? | ||
new SingleStepSolution(eliminations.Distinct().ToArray(), strategyName) : | ||
null; | ||
} | ||
|
||
/// <summary> | ||
/// Returns a dictionary where key is one of <see cref="SudokuPuzzle.PossibleCellValues"/> | ||
/// and value is the collection of cells containing that value, but only if the <paramref name="cellsWithNoValue"/> | ||
/// contains a certain number of such cells (<paramref name="numberOfCellsContainingValue"/>). | ||
/// </summary> | ||
/// <param name="cellsWithNoValue">Empty cells of a single row/column/block.</param> | ||
/// <param name="sudokuPuzzle">Sudoku puzzle.</param> | ||
/// <param name="numberOfCellsContainingValue">Tells how many cells containing a value we are looking for.</param> | ||
/// <returns>See summary.</returns> | ||
protected IDictionary<int, Cell[]> GetHiddenCandidates(Cell[] cellsWithNoValue, SudokuPuzzle sudokuPuzzle, | ||
params int[] numberOfCellsContainingValue) | ||
{ | ||
var candidates = new Dictionary<int, Cell[]>(); | ||
|
||
foreach (var cellValue in sudokuPuzzle.PossibleCellValues) | ||
{ | ||
var valueInCells = cellsWithNoValue.Where(x => x.CanBe.Contains(cellValue)).ToArray(); | ||
if (numberOfCellsContainingValue.Contains(valueInCells.Length)) | ||
candidates.Add(cellValue, valueInCells); | ||
} | ||
|
||
return candidates; | ||
} | ||
|
||
/// <summary> | ||
/// For each member of<paramref name="cell"/>'s <see cref="Cell.CanBe"/>: | ||
/// - if member is part of <paramref name="valuesToExclude"/>, ignore it | ||
/// - if member is not part of <paramref name="valuesToExclude"/> return it as an elimination | ||
/// </summary> | ||
/// <param name="cell">Cell which is analyzed for eliminations.</param> | ||
/// <param name="sudokuPuzzle">Sudoku puzzle to which the <paramref name="cell"/> belongs.</param> | ||
/// <param name="valuesToExclude">Values which are not elimination candidates.</param> | ||
/// <returns>See summary.</returns> | ||
protected IEnumerable<SingleStepSolution.Candidate> GetEliminations( | ||
Cell cell, SudokuPuzzle sudokuPuzzle, params int[] valuesToExclude) | ||
{ | ||
var eliminations = new List<SingleStepSolution.Candidate>(); | ||
var cellIndex = sudokuPuzzle.GetCellIndex(cell); | ||
var eliminatedValues = cell.CanBe.Except(valuesToExclude); | ||
foreach (var eliminatedValue in eliminatedValues) | ||
{ | ||
eliminations.Add(new SingleStepSolution.Candidate(cellIndex.RowIndex, cellIndex.ColumnIndex, eliminatedValue)); | ||
} | ||
|
||
return eliminations; | ||
} | ||
} | ||
} |
Oops, something went wrong.