diff --git a/SudokuSolver.Tests/Solvers/BacktrackSolvers/Pruners/HiddenTripplePrunerTests.cs b/SudokuSolver.Tests/Solvers/BacktrackSolvers/Pruners/HiddenTripplePrunerTests.cs new file mode 100644 index 0000000..18812ab --- /dev/null +++ b/SudokuSolver.Tests/Solvers/BacktrackSolvers/Pruners/HiddenTripplePrunerTests.cs @@ -0,0 +1,30 @@ +using SudokuSolver.Models; +using SudokuSolver.Solvers.BacktrackSolvers.Pruners; +using SudokuSolver.Solvers.Preprocessors; + +namespace SudokuSolver.Tests.Solvers.BacktrackSolvers.Pruners +{ + [TestClass] + public class HiddenTripplePrunerTests + { + [TestMethod] + [DataRow("000001030231090000065003100678924300103050006000136700009360570006019843300000000", 15)] + public void Can_PruneCorrectly(string board, int expectedChange) + { + // ARRANGE + var values = new List(); + foreach (var c in board) + values.Add(byte.Parse($"{c}")); + var context = Preprocessor.Preprocess(new SudokuBoard(values.ToArray())); + IPruner pruner1 = new HiddenTripplePruner(); + var preCount = context.Cardinalities.Sum(x => x.Possibilities); + + // ACT + while (pruner1.Prune(context)) { } + context.Cardinalities = Preprocessor.GenerateCardinalities(context.Board, context.Candidates); + + // ASSERT + Assert.AreEqual(expectedChange, preCount - context.Cardinalities.Sum(x => x.Possibilities)); + } + } +} diff --git a/SudokuSolver/Solvers/BacktrackSolvers/BacktrackSolver.cs b/SudokuSolver/Solvers/BacktrackSolvers/BacktrackSolver.cs index 77424c7..1bf5fb8 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/BacktrackSolver.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/BacktrackSolver.cs @@ -12,6 +12,7 @@ public class BacktrackSolver : BaseSolver new NakedPairPruner(), new NakedTripplePruner(), new HiddenPairPruner(), + new HiddenTripplePruner(), new PointingPairsPruner() }; diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/BasePruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/BasePruner.cs index cae8f32..fea6fe7 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/BasePruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/BasePruner.cs @@ -19,6 +19,22 @@ internal List GetAssignmentsFromBlock(SearchContext context, byt return cellPossibilities; } + internal List GetAssignmentsFromRow(SearchContext context, byte row) + { + var cellPossibilities = new List(); + for (int x = 0; x < SudokuBoard.BoardSize; x++) + cellPossibilities.AddRange(context.Candidates[x, row]); + return cellPossibilities; + } + + internal List GetAssignmentsFromColumn(SearchContext context, byte column) + { + var cellPossibilities = new List(); + for (int y = 0; y < SudokuBoard.BoardSize; y++) + cellPossibilities.AddRange(context.Candidates[column, y]); + return cellPossibilities; + } + internal int PruneValueCandidatesFromRow(SearchContext context, List ignore, byte value) { var pruned = 0; diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/CertainsPruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/CertainsPruner.cs index 7b51301..0aaab30 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/CertainsPruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/CertainsPruner.cs @@ -5,28 +5,26 @@ namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners public class CertainsPruner : BasePruner { public override bool Prune(SearchContext context) - { - bool any = false; - while (PruneCertains(context)) { any = true; } - return any; - } - - private bool PruneCertains(SearchContext context) { var pruned = 0; - for (byte x = 0; x < SudokuBoard.BoardSize; x++) - for (byte y = 0; y < SudokuBoard.BoardSize; y++) - if (context.Candidates[x, y].Count == 1) - pruned += RemoveCandidate(context, context.Candidates[x, y][0]); - - for (byte blockX = 0; blockX < SudokuBoard.Blocks; blockX++) + var pre = 1; + while (pruned - pre != 0) { - for (byte blockY = 0; blockY < SudokuBoard.Blocks; blockY++) + pre = pruned; + for (byte x = 0; x < SudokuBoard.BoardSize; x++) + for (byte y = 0; y < SudokuBoard.BoardSize; y++) + if (context.Candidates[x, y].Count == 1) + pruned += RemoveCandidate(context, context.Candidates[x, y][0]); + + for (byte blockX = 0; blockX < SudokuBoard.Blocks; blockX++) { - var cellPossibilities = GetAssignmentsFromBlock(context, blockX, blockY); - for (byte i = 1; i <= SudokuBoard.BoardSize; i++) - if (cellPossibilities.Count(x => x.Value == i) == 1) - pruned += RemoveCandidate(context, cellPossibilities.First(x => x.Value == i)); + for (byte blockY = 0; blockY < SudokuBoard.Blocks; blockY++) + { + var cellPossibilities = GetAssignmentsFromBlock(context, blockX, blockY); + for (byte i = 1; i <= SudokuBoard.BoardSize; i++) + if (cellPossibilities.Count(x => x.Value == i) == 1) + pruned += RemoveCandidate(context, cellPossibilities.First(x => x.Value == i)); + } } } if (pruned > 0) diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenPairPruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenPairPruner.cs index 49cd13a..7de8a44 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenPairPruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenPairPruner.cs @@ -5,13 +5,6 @@ namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners public class HiddenPairPruner : BasePruner { public override bool Prune(SearchContext context) - { - bool any = false; - while (PruneHiddenPairs(context)) { any = true; } - return any; - } - - private bool PruneHiddenPairs(SearchContext context) { var pruned = 0; for (byte blockX = 0; blockX < SudokuBoard.Blocks; blockX++) diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenTripplePruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenTripplePruner.cs new file mode 100644 index 0000000..4ad4507 --- /dev/null +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/HiddenTripplePruner.cs @@ -0,0 +1,76 @@ +using SudokuSolver.Models; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners +{ + public class HiddenTripplePruner : BasePruner + { + public override bool Prune(SearchContext context) + { + var pruned = 0; + + for (byte row = 0; row < SudokuBoard.BoardSize; row++) + { + var candidates = GetAssignmentsFromRow(context, row); + var removed = 1; + while (removed > 0) + { + removed = 0; + for (int i = 1; i <= SudokuBoard.BoardSize; i++) + if (candidates.Where(x => x.Value == i).Count() == 1 || candidates.Where(x => x.Value == i).Count() > 3) + removed += candidates.RemoveAll(x => x.Value == i); + for (int i = 1; i <= SudokuBoard.BoardSize; i++) + if (candidates.Where(x => x.Value == i).Any(y => !candidates.Any(z => z != y && z.X == y.X))) + removed += candidates.RemoveAll(x => x.Value == i); + } + + if (candidates.DistinctBy(x => x.Value).Count() == 3 && candidates.DistinctBy(x => x.X).Count() == 3) + foreach (var candidate in candidates) + pruned += context.Candidates[candidate.X, candidate.Y].RemoveAll(x => !candidates.Contains(x)); + } + + for (byte column = 0; column < SudokuBoard.BoardSize; column++) + { + var candidates = GetAssignmentsFromColumn(context, column); + var removed = 1; + while(removed > 0) + { + removed = 0; + for (int i = 1; i <= SudokuBoard.BoardSize; i++) + if (candidates.Where(x => x.Value == i).Count() == 1 || candidates.Where(x => x.Value == i).Count() > 3) + removed += candidates.RemoveAll(x => x.Value == i); + for (int i = 1; i <= SudokuBoard.BoardSize; i++) + if (candidates.Where(x => x.Value == i).Any(y => !candidates.Any(z => z != y && z.Y == y.Y))) + removed += candidates.RemoveAll(x => x.Value == i); + } + + if (candidates.DistinctBy(x => x.Value).Count() == 3 && candidates.DistinctBy(x => x.Y).Count() == 3) + foreach (var candidate in candidates) + pruned += context.Candidates[candidate.X, candidate.Y].RemoveAll(x => !candidates.Contains(x)); + } + + if (pruned > 0) + Console.WriteLine($"Removed {pruned} candidates because of hidden tripples"); + return pruned > 0; + } + + private List LegalTripple(List assignments) + { + if (assignments.DistinctBy(x => x.Value).Count() != 3) + return new List(); + + for(int i = 1; i <= SudokuBoard.BoardSize; i++) + { + if (assignments.Any(x => x.Value == i) && assignments.Where(x => x.Value == i).Count() < 2) + return new List(); + } + + return assignments; + } + } +} diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedPairPruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedPairPruner.cs index 690b8bb..dbe43c8 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedPairPruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedPairPruner.cs @@ -5,13 +5,6 @@ namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners public class NakedPairPruner : BasePruner { public override bool Prune(SearchContext context) - { - var any = false; - while (PruneNakedPairs(context)) { any = true; } - return any; - } - - private bool PruneNakedPairs(SearchContext context) { var pruned = 0; // Prune from columns @@ -70,7 +63,7 @@ private bool PruneNakedPairs(SearchContext context) var fromY = blockY * SudokuBoard.Blocks; var toY = (blockY + 1) * SudokuBoard.Blocks; var cellPossibilities = new List>(); - for (int i = 0; i <= SudokuBoard.BoardSize; i++) + for (int i = 0; i < SudokuBoard.BoardSize; i++) cellPossibilities.Add(new List()); int offset = 0; diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedTripplePruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedTripplePruner.cs index 69d588f..90aee2d 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedTripplePruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/NakedTripplePruner.cs @@ -5,13 +5,6 @@ namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners public class NakedTripplePruner : BasePruner { public override bool Prune(SearchContext context) - { - var any = false; - while (PruneNakedTripples(context)) { any = true; } - return any; - } - - private bool PruneNakedTripples(SearchContext context) { var pruned = 0; diff --git a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/PointingPairsPruner.cs b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/PointingPairsPruner.cs index 068767b..e3daab0 100644 --- a/SudokuSolver/Solvers/BacktrackSolvers/Pruners/PointingPairsPruner.cs +++ b/SudokuSolver/Solvers/BacktrackSolvers/Pruners/PointingPairsPruner.cs @@ -5,13 +5,6 @@ namespace SudokuSolver.Solvers.BacktrackSolvers.Pruners public class PointingPairsPruner : BasePruner { public override bool Prune(SearchContext context) - { - var any = false; - while (PrunePointingPairs(context)) { any = true; } - return any; - } - - private bool PrunePointingPairs(SearchContext context) { var pruned = 0;