public void TestInitializeConstraints() { // This board is solvable from the constraints alone var cpb = new CPBoard(bSolvableByCP); Assert.True(CPSolver.InitializeConstraints(cpb)); Assert.True(CPSolver.IsSolved(cpb)); cpb = new CPBoard(bInvalid); Assert.False(CPSolver.InitializeConstraints(cpb)); cpb = new CPBoard(bEmpty); Assert.True(CPSolver.InitializeConstraints(cpb)); for (int i = 0; i < Const.N2; i++) { Assert.Equal("123456789", cpb.Get(i)); } cpb = new CPBoard("1234" + bEmpty.Remove(0, 4)); CPSolver.InitializeConstraints(cpb); Assert.Equal("1", cpb.Get(0)); Assert.Equal("2", cpb.Get(1)); Assert.Equal("3", cpb.Get(2)); Assert.Equal("4", cpb.Get(3)); Assert.Equal("56789", cpb.Get(4)); // TODO: Double check results cpb = new CPBoard(bSolvable); CPSolver.InitializeConstraints(cpb); Assert.Equal("1458", cpb.Get(2)); }
public void TestSolve() { var cpb = new CPBoard(bSolvableByCP); var sr = CPSolver.Solve(cpb); Assert.True(sr.DidSolve); Assert.True(CPSolver.IsSolved(sr.CPB)); cpb = new CPBoard(bSolved); sr = CPSolver.Solve(cpb); Assert.True(sr.DidSolve); Assert.Equal("7", cpb.Get(0)); Assert.True(CPSolver.IsSolved(sr.CPB)); // Double check results cpb = new CPBoard(bSolvable); sr = CPSolver.Solve(cpb); Assert.True(sr.DidSolve); Assert.True(CPSolver.IsSolved(sr.CPB)); cpb = new CPBoard(bEmpty); sr = CPSolver.Solve(cpb); Assert.True(sr.DidSolve); Assert.True(CPSolver.IsSolved(sr.CPB)); }
/// <summary> /// Eliminate will remove the value from the tile and propagate changes via calls to Assign /// if there is only a single value remaining /// NOTE: This method mutates the CPBoard cpb parameter /// </summary> /// <param name="cpb"></param> /// <param name="tile"></param> /// <param name="value"></param> /// <returns></returns> static public bool Eliminate(CPBoard cpb, int tileIndex, string value) { var values = cpb.Get(tileIndex); CDebug.Assert(values.Length > 0, "values.Length > 0"); if (values.Length == 1) { // Reached a contradiction, eliminating this value will leave the tile EMPTY! if (values == value) { return(false); } // Value has already been eliminated from this tile return(true); } var newValues = values.Replace(value, ""); cpb.Set(tileIndex, newValues); // If there is only one possible value after the change // Call assign to propagate changes if (newValues.Length == 1) { return(Assign(cpb, tileIndex, newValues)); } return(true); }
/// <summary> /// Exhaustively try all possible values for the tiles, using constraints to /// reduce the search space /// </summary> /// <param name="cpb"></param> /// <param name="depth"></param> Parameter debugging and curiousity /// <returns></returns> static private SearchResult search(CPBoard cpb, int depth) { var freeTile = cpb.GetFreeTile(); // No free tile available if (freeTile.Value == null) { return(new SearchResult(cpb, IsSolved(cpb))); } var possibleValues = freeTile.Value; foreach (var c in possibleValues) { // Make a copy in case we reach contradiction and need to revert back var copy = cpb.Copy(); bool didAssign = Assign(copy, freeTile.Key, c.ToString()); CDebug.Assert(copy.Get(freeTile.Key) != cpb.Get(freeTile.Key), "Original CPB mutated!"); if (didAssign) { SearchResult sr = search(copy, depth + 1); if (sr.DidSolve) { return(sr); } } } return(new SearchResult(cpb, false)); }
public void TestBasicEliminate() { var cpb = new CPBoard(bEmpty); CPSolver.Eliminate(cpb, 0, "1"); Assert.Equal("23456789", cpb.Get(0)); }
/// <summary> /// Helper function for the search function /// </summary> /// <param name="cpb"></param> /// <returns></returns> static public SearchResult Solve(CPBoard cpb) { if (!InitializeConstraints(cpb)) { return(new SearchResult(cpb, false)); } return(search(cpb, 0)); }
/// <summary> /// For a the puzzle to be solved, each tile within a unit must have distinct value /// A unit is either: /// - a row (1x9) /// - a column (9x1) /// - a square (3x3) /// </summary> /// <returns></returns> static public bool IsSolved(CPBoard cpb) { foreach (var unit in Units) { var values = string.Join("", unit.Select(tileIndex => cpb.Get(tileIndex))); if (values.Distinct().Count() != values.Count()) { return(false); } } return(true); }
public void TestIsSolved() { var cpb = new CPBoard(bSolvableByCP); Assert.False(CPSolver.IsSolved(cpb)); cpb = new CPBoard(bSolved); Assert.True(CPSolver.IsSolved(cpb)); cpb = new CPBoard(bInvalid); Assert.False(CPSolver.IsSolved(cpb)); }
public void TestFreeTile() { var cpb = new CPBoard(bSolvableByCP); var ft = cpb.GetFreeTile(); Assert.Equal(0, ft.Key); Assert.Equal("123456789", ft.Value); // No elimination has occured yet cpb = new CPBoard(bSolved); ft = cpb.GetFreeTile(); Assert.Equal(0, ft.Key); // TODO: Really need maybe type, 0 is not a good default Assert.Null(ft.Value); }
public void TestCopy() { var cpb = new CPBoard(bSolvableByCP); CPBoard copy = cpb.Copy(); // Changing original does not mutate copy Assert.NotEqual("4", copy.Get(0)); cpb.Set(0, "4"); Assert.NotEqual("4", copy.Get(0)); // Chaning copy does not change original Assert.NotEqual("2", cpb.Get(1)); copy.Set(1, "2"); Assert.NotEqual("2", cpb.Get(1)); }
/// <summary> /// This function should be called on a initially "unconstrained" CPBoard /// before the solving step, however it is not set to private because /// it is fun to see the incremental effects of constraining values before searching /// </summary> /// <param name="cpb"></param> /// <returns></returns> static internal bool InitializeConstraints(CPBoard cpb) { for (int i = 0; i < Const.N2; i++) { var value = cpb.Get(i); if (value.Length == 1) { if (!Assign(cpb, i, value)) { return(false); } } } return(true); }
public void TestInvalidAssign() { var cpb = new CPBoard(bEmpty); CPSolver.Assign(cpb, 0, "1"); Assert.False(CPSolver.Assign(cpb, 1, "1"), "Made invalid assignment!"); Assert.Equal("23456789", cpb.Get(1)); // If assignment fails do not mutate CPBoard Assert.False(CPSolver.Assign(cpb, 0, "2"), "Assigned an impossible value"); // Check that invalid assignment did not propagates foreach (var peerIndex in CPSolver.Peers[0]) { Assert.Equal("23456789", cpb.Get(peerIndex)); } }
public void TestAssignEliminate() { var cpb = new CPBoard(bEmpty); CPSolver.Assign(cpb, 0, "1"); Assert.Equal("1", cpb.Get(0)); // Check assignment propagates foreach (var peerIndex in CPSolver.Peers[0]) { Assert.Equal("23456789", cpb.Get(peerIndex)); } // Check overlap elimination CPSolver.Assign(cpb, Const.N2 - 1, "9"); // Last tile Assert.Equal("2345678", cpb.Get(Const.N - 1)); // 8 Assert.Equal("2345678", cpb.Get(Const.N * Const.N - Const.N)); // 72 }
/// <summary> /// Assign a single value to the tile, propagate the changes via calls to Eliminate /// NOTE: Method mutates the CPBoard cpb parameter /// </summary> /// <param name="cpb"></param> /// <param name="tileIndex"></param> /// <param name="value"></param> /// <returns></returns> static public bool Assign(CPBoard cpb, int tileIndex, string value) { CDebug.Assert(value.Length == 1, "Cannot assign multiple values"); var values = cpb.Get(tileIndex); // Cannot assign a value that is a not possible value of the tile if (!values.Contains(value)) { return(false); } // Attempt to eliminate values from peers cpb.Set(tileIndex, value); foreach (var peer in Peers[tileIndex]) { if (!Eliminate(cpb, peer, value)) { return(false); } } return(true); }
/// <summary> /// Deep copy function that uses the private constructor /// Useful reverting calls from Assign and Eliminate /// Copy function is a convience function for the private constructor /// </summary> /// <returns></returns> public CPBoard Copy() { CPBoard copy = new CPBoard(Tiles); return(copy); }
static void FakeMain(string[] args) { Console.WriteLine(string.Join(',', args)); if (args.Length > 0) { IList <string> gridStrings = new List <string> (); string line; string fileName = @"puzzles.txt"; System.IO.StreamReader file = new System.IO.StreamReader(fileName); while ((line = file.ReadLine()) != null) { if (line.Contains("Grid")) { continue; } else { gridStrings.Add(line.Trim()); } System.Console.WriteLine(line); } file.Close(); System.Console.WriteLine("{0} in {1}", gridStrings.Count, fileName); // TODO: Do initial solve to reduce jitter Console.WriteLine("Starting benchmarking"); var solveTimes = new List <double> (new double[gridStrings.Count]); Stopwatch sw = new Stopwatch(); int numIterations = 10; for (int i = 0; i < numIterations; i++) { for (int j = 0; j < gridStrings.Count; j++) { var cpb = new CPBoard(gridStrings[j]); cpb.Print(); Console.WriteLine(new String('-', 20)); sw.Restart(); var sr = CPSolver.Solve(cpb); sw.Stop(); double ticks = sw.ElapsedTicks; double seconds = ticks / Stopwatch.Frequency; double milliseconds = (ticks / Stopwatch.Frequency) * 1000; solveTimes[j] += milliseconds; if (!sr.DidSolve) { throw new Exception($"Could not solve:\n {cpb.ToString()}"); } sr.CPB.Print(); } } var averageTimes = solveTimes.Select(st => st / numIterations).ToList(); var averageTime = averageTimes.Average(); Console.WriteLine($"Average solve time was: {averageTime}"); for (int i = 0; i < averageTimes.Count; i++) { Console.WriteLine($"Grid {i} : {averageTimes[i]}"); } } else { string line; // Handle piped input Console.SetIn(new System.IO.StreamReader(Console.OpenStandardInput(8192))); // This will allow input >256 chars while (Console.In.Peek() != -1) { line = Console.In.ReadLine(); if (line.Contains("Grid")) { var puzzleName = line; Console.WriteLine($"Attempting ${puzzleName}"); } else { var cpb = new CPBoard(line); cpb.Print(); Console.WriteLine(new String('-', 20)); var sr = CPSolver.Solve(cpb); if (!sr.DidSolve) { throw new Exception($"Could not solve:\n {cpb.ToString()}"); } sr.CPB.Print(); } } } }
public SearchResult(CPBoard cpb, bool didSolve) { CPB = cpb; DidSolve = didSolve; }