Skip to content

Commit

Permalink
Implemented HiddenPair, HiddenTriple and HiddenQuad solving strategie…
Browse files Browse the repository at this point in the history
…s + tests.
  • Loading branch information
kurtanr committed Aug 13, 2019
1 parent 0bf3093 commit a9d5e95
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 1 deletion.
80 changes: 80 additions & 0 deletions SimpleSudokuSolver.Tests/Strategy/HiddenPairTests.cs
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);
}
}
}
83 changes: 83 additions & 0 deletions SimpleSudokuSolver.Tests/Strategy/HiddenQuadTests.cs
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);
}
}
}
52 changes: 52 additions & 0 deletions SimpleSudokuSolver.Tests/Strategy/HiddenTripleTests.cs
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);
}
}
}
5 changes: 4 additions & 1 deletion SimpleSudokuSolver/DefaultSolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public DefaultSolver(params ISudokuSolverStrategy[] strategies)
new LockedCandidates(),
new NakedPair(),
new NakedTriple(),
new NakedQuad()
new NakedQuad(),
new HiddenPair(),
new HiddenTriple(),
new HiddenQuad()
};
}
}
Expand Down
50 changes: 50 additions & 0 deletions SimpleSudokuSolver/Strategy/HiddenPair.cs
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;
}
}
}
83 changes: 83 additions & 0 deletions SimpleSudokuSolver/Strategy/HiddenPairTripleQuadBase.cs
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;
}
}
}
Loading

0 comments on commit a9d5e95

Please sign in to comment.