Skip to content

Commit 2a4e625

Browse files
committed
Implemented NakedQuad solving strategy.
1 parent ca0fb60 commit 2a4e625

File tree

4 files changed

+208
-1
lines changed

4 files changed

+208
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ Out of the well known [Sudoku Solving Techniques](https://sudoku9x9.com/sudoku_s
2121
* Locked Candidates
2222
* Naked Pair
2323
* Naked Triple
24+
* Naked Quad
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using NUnit.Framework;
2+
using SimpleSudokuSolver.Model;
3+
using SimpleSudokuSolver.Strategy;
4+
5+
namespace SimpleSudokuSolver.Tests.Strategy
6+
{
7+
public class NakedQuadTests : BaseStrategyTest
8+
{
9+
private readonly ISudokuSolverStrategy _strategy = new NakedQuad();
10+
11+
[Test]
12+
public void NakedQuadTest1()
13+
{
14+
var sudoku = new int[,]
15+
{
16+
// From: http://www.sudokuwiki.org/Naked_Candidates
17+
// Contains one naked quad in block, and after that, one naked quad in row
18+
{ 0,0,0,0,3,0,0,8,6 },
19+
{ 0,0,0,0,2,0,0,4,0 },
20+
{ 0,9,0,0,7,8,5,2,0 },
21+
{ 3,7,1,8,5,6,2,9,4 },
22+
{ 9,0,0,1,4,2,3,7,5 },
23+
{ 4,0,0,3,9,7,6,1,8 },
24+
{ 2,0,0,7,0,3,8,5,9 },
25+
{ 0,3,9,2,0,5,4,6,7 },
26+
{ 7,0,0,9,0,4,1,3,2 }
27+
};
28+
29+
var sudokuPuzzle = new SudokuPuzzle(sudoku);
30+
SolveUsingStrategy(sudokuPuzzle, _strategy);
31+
32+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 1].CanBe, 1);
33+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 1].CanBe, 5);
34+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 2].CanBe, 5);
35+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 5);
36+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 6);
37+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 8);
38+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[2, 2].CanBe, 6);
39+
}
40+
41+
[Test]
42+
public void NakedQuadTest2()
43+
{
44+
var sudoku = new int[,]
45+
{
46+
// From: http://www.manifestmaster.com/jetsudoku/nakedQuad.html
47+
// Naked quad in column - must first use LockedCandidates to eliminate some candidates
48+
{ 0,0,0,0,9,0,0,0,0 },
49+
{ 0,0,0,0,3,1,6,0,0 },
50+
{ 0,0,0,0,4,8,0,9,0 },
51+
{ 7,1,9,8,6,3,4,5,2 },
52+
{ 6,0,0,0,7,0,0,3,0 },
53+
{ 2,0,0,0,1,0,7,6,0 },
54+
{ 1,0,0,0,2,0,0,8,6 },
55+
{ 8,6,0,0,5,9,2,0,0 },
56+
{ 0,0,0,1,8,6,0,4,5 }
57+
};
58+
59+
var sudokuPuzzle = new SudokuPuzzle(sudoku);
60+
SolveUsingStrategy(sudokuPuzzle, new LockedCandidates());
61+
SolveUsingStrategy(sudokuPuzzle, _strategy);
62+
63+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 2].CanBe, 3);
64+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 2].CanBe, 4);
65+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 2].CanBe, 5);
66+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[0, 2].CanBe, 8);
67+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 4);
68+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 5);
69+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[1, 2].CanBe, 8);
70+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[2, 2].CanBe, 3);
71+
CollectionAssert.DoesNotContain(sudokuPuzzle.Cells[2, 2].CanBe, 5);
72+
}
73+
}
74+
}

SimpleSudokuSolver/DefaultSolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public DefaultSolver(params ISudokuSolverStrategy[] strategies)
3535
new NakedSingle(),
3636
new LockedCandidates(),
3737
new NakedPair(),
38-
new NakedTriple()
38+
new NakedTriple(),
39+
new NakedQuad()
3940
};
4041
}
4142
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using SimpleSudokuSolver.Model;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
6+
namespace SimpleSudokuSolver.Strategy
7+
{
8+
/// <summary>
9+
/// Strategy looks for four cells in the same row / column / block that contain IN TOTAL four candidates.
10+
/// Each of the four cells can contain two, three or four candidates.
11+
/// If such four cells are found, then the four candidate values cannot be in any other cell in the same row / column / block.
12+
/// </summary>
13+
/// <remarks>
14+
/// See also:
15+
/// - https://sudoku9x9.com/naked_pair.html
16+
/// - http://www.sudokuwiki.org/Naked_Candidates
17+
/// </remarks>
18+
public class NakedQuad : ISudokuSolverStrategy
19+
{
20+
public string StrategyName => "Naked Quad";
21+
22+
public SingleStepSolution SolveSingleStep(SudokuPuzzle sudokuPuzzle)
23+
{
24+
var eliminations = new List<SingleStepSolution.Candidate>();
25+
26+
foreach (var row in sudokuPuzzle.Rows)
27+
{
28+
eliminations.AddRange(GetNakedQuadEliminations(row.Cells, sudokuPuzzle));
29+
}
30+
31+
foreach (var column in sudokuPuzzle.Columns)
32+
{
33+
eliminations.AddRange(GetNakedQuadEliminations(column.Cells, sudokuPuzzle));
34+
}
35+
36+
foreach (var block in sudokuPuzzle.Blocks)
37+
{
38+
eliminations.AddRange(GetNakedQuadEliminations(block.Cells.OfType<Cell>(), sudokuPuzzle));
39+
}
40+
41+
return eliminations.Count > 0 ?
42+
new SingleStepSolution(eliminations.Distinct().ToArray(), StrategyName) :
43+
null;
44+
}
45+
46+
private IEnumerable<SingleStepSolution.Candidate> GetNakedQuadEliminations(IEnumerable<Cell> cells, SudokuPuzzle sudokuPuzzle)
47+
{
48+
var cellsWithNoValue = cells.Where(x => !x.HasValue).ToArray();
49+
var eliminations = new List<SingleStepSolution.Candidate>();
50+
51+
// we need to have at least 4 cells which have 2, 3 or 4 possible potential values
52+
var nakedQuadCandidates = cellsWithNoValue.Where(x => x.CanBe.Count >= 2 && x.CanBe.Count <= 4).ToArray();
53+
if (nakedQuadCandidates.Length < 4)
54+
return eliminations;
55+
56+
for (int i = 0; i < nakedQuadCandidates.Length - 3; i++)
57+
{
58+
Cell first = nakedQuadCandidates[i];
59+
60+
for (int j = i + 1; j < nakedQuadCandidates.Length - 2; j++)
61+
{
62+
Cell second = nakedQuadCandidates[j];
63+
64+
for (int k = j + 1; k < nakedQuadCandidates.Length - 1; k++)
65+
{
66+
Cell third = nakedQuadCandidates[k];
67+
68+
for (int m = k + 1; m < nakedQuadCandidates.Length; m++)
69+
{
70+
Cell fourth = nakedQuadCandidates[m];
71+
72+
var distinctPotentialCellValuesInCandidates = GetDistinctPotentialCellValuesInCandidates(
73+
first.CanBe, second.CanBe, third.CanBe, fourth.CanBe);
74+
75+
if (distinctPotentialCellValuesInCandidates.Length == 4)
76+
{
77+
var nakedQuad = new Tuple<Cell, Cell, Cell, Cell>(first, second, third, fourth);
78+
79+
eliminations.AddRange(GetNakedQuadEliminationsCore(
80+
nakedQuad, distinctPotentialCellValuesInCandidates, cellsWithNoValue, sudokuPuzzle));
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
return eliminations;
88+
}
89+
90+
private IEnumerable<SingleStepSolution.Candidate> GetNakedQuadEliminationsCore(
91+
Tuple<Cell, Cell, Cell, Cell> nakedQuad, int[] distinctPotentialCellValuesInCandidates,
92+
Cell[] cellsWithNoValue, SudokuPuzzle sudokuPuzzle)
93+
{
94+
var eliminations = new List<SingleStepSolution.Candidate>();
95+
96+
foreach (var cellWithNoValue in cellsWithNoValue)
97+
{
98+
if (nakedQuad.Item1 == cellWithNoValue || nakedQuad.Item2 == cellWithNoValue ||
99+
nakedQuad.Item3 == cellWithNoValue || nakedQuad.Item4 == cellWithNoValue)
100+
continue;
101+
102+
var finalItems = cellWithNoValue.CanBe.Intersect(distinctPotentialCellValuesInCandidates).ToArray();
103+
104+
if (finalItems.Length > 0)
105+
{
106+
foreach (var finalItem in finalItems)
107+
{
108+
var (RowIndex, ColumnIndex) = sudokuPuzzle.GetCellIndex(cellWithNoValue);
109+
eliminations.Add(new SingleStepSolution.Candidate(RowIndex, ColumnIndex, finalItem));
110+
}
111+
}
112+
}
113+
return eliminations;
114+
}
115+
116+
/// <summary>
117+
/// Flattens the array of enumerables into a single array and returns unique items of that array (no repetition).
118+
/// </summary>
119+
private int[] GetDistinctPotentialCellValuesInCandidates(params IEnumerable<int>[] items)
120+
{
121+
List<int> result = new List<int>();
122+
123+
foreach (var item in items)
124+
{
125+
result.AddRange(item);
126+
}
127+
128+
return result.Distinct().ToArray();
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)