public static Grid BuildFromDefinition(string definition) { var grid = new Grid {Squares = new Square[9, 9]}; var rows = definition.Split(new[] {@" "}, StringSplitOptions.RemoveEmptyEntries); if (rows.Length != 10) { var errorMessage = string.Format("Definition should contain 10 rows: 1 title then 9 rows of squares.{0}Found {1} rows in {2}", Environment.NewLine, rows.Length, definition); throw new ApplicationException(errorMessage); } for (var rowIdx = 1; rowIdx < rows.Length; ++rowIdx) { for (var colIdx = 0; colIdx < rows[rowIdx].Length; ++colIdx) { var digit = (int)Char.GetNumericValue(rows[rowIdx][colIdx]); var square = new Square(); //grid.SetDigit(rowIdx, colIdx, digit); square.SetDigit(digit); grid.Squares[rowIdx - 1, colIdx] = square; } } return grid; }
public void Solve(Grid grid) { for (var colIdx = 0; colIdx < 9; ++colIdx) { var fixedDigits = new HashSet<int>(); for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) { fixedDigits.Add(grid.Squares[rowIdx, colIdx].Digit); } } for (var digitToPlace = 1; digitToPlace <= 9; ++digitToPlace) { if (fixedDigits.Contains(digitToPlace)) continue; var possibleLocations = new List<int>(); for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) continue; if (grid.Squares[rowIdx, colIdx].PossibleDigits.Contains(digitToPlace)) { possibleLocations.Add(rowIdx); } } if (possibleLocations.Count == 1) { grid.SetDigit(possibleLocations[0], colIdx, digitToPlace); } } } }
private static void Solve(Grid grid, ISolver[] solvers, bool useBruteForceFallback) { var unsolvedSquares = 81; var retryCount = 0; // Whilst having an effect on the grid i.e. solving at least one square each iteration // - not checking for the effect of removing possibilities so give it a retry while (retryCount < 2) { while (grid.UnsolvedSquareCount > 0 && grid.UnsolvedSquareCount < unsolvedSquares) { unsolvedSquares = grid.UnsolvedSquareCount; foreach (var solver in solvers) { solver.Solve(grid); grid.Tidy(); } } retryCount++; } if (grid.IsSolved) return; if (!useBruteForceFallback) return; var bruteForceSolver = new BruteForceSolver(); bruteForceSolver.Solve(grid); }
public void Solve(Grid grid) { for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { for (var colIdx = 0; colIdx < 9; ++colIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) continue; if (grid.Squares[rowIdx, colIdx].PossibleDigits.Count != 1) continue; var digitToSet = grid.Squares[rowIdx, colIdx].PossibleDigits.First(); grid.SetDigit(rowIdx, colIdx, digitToSet); } } }
public void Solve(Grid grid) { SolveForSquare(grid, 0, 2, 0, 2); SolveForSquare(grid, 0, 2, 3, 5); SolveForSquare(grid, 0, 2, 6, 8); SolveForSquare(grid, 3, 5, 0, 2); SolveForSquare(grid, 3, 5, 3, 5); SolveForSquare(grid, 3, 5, 6, 8); SolveForSquare(grid, 6, 8, 0, 2); SolveForSquare(grid, 6, 8, 3, 5); SolveForSquare(grid, 6, 8, 6, 8); }
/// <summary> /// Given a partially solved grid, try each remaining possibility in turn and backtrack if necessary /// </summary> /// <param name="grid"></param> public void Solve(Grid grid) { var workingGrid = grid.Clone(); var unsolvedSquares = GetUnsolvedSquarePossibilities(grid); for (var remainingSquareIdx = 0; remainingSquareIdx < unsolvedSquares.Count; ++remainingSquareIdx) { var foundSquareSolution = false; var currentUnsolvedSquare = unsolvedSquares[remainingSquareIdx]; foreach (var possibleDigit in currentUnsolvedSquare.PossibleDigits) { if (possibleDigit > workingGrid.Squares[currentUnsolvedSquare.RowIdx, currentUnsolvedSquare.ColIdx].Digit) { var isValidToSet = workingGrid.EnsureValidToSetTheDigit(currentUnsolvedSquare.RowIdx, currentUnsolvedSquare.ColIdx, possibleDigit, false); if (isValidToSet) { workingGrid.Squares[currentUnsolvedSquare.RowIdx, currentUnsolvedSquare.ColIdx].SetDigit(possibleDigit); foundSquareSolution = true; } } if (foundSquareSolution) break; } // If have a working solution for the current unsolved square, move to the next if (foundSquareSolution) continue; // Otherwise roll back workingGrid.Squares[currentUnsolvedSquare.RowIdx, currentUnsolvedSquare.ColIdx].ClearDigit(); remainingSquareIdx = remainingSquareIdx - 2; // Move to the next possibility for the previous unsolved square (for loop moves forward one again) if (remainingSquareIdx < -1) break; } if (!workingGrid.IsSolved) return; // Copy the newly solved squares over for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { for (var colIdx = 0; colIdx < 9; ++colIdx) { if (!grid.Squares[rowIdx, colIdx].IsSolved) { grid.SetDigit(rowIdx, colIdx, workingGrid.Squares[rowIdx, colIdx].Digit); } } } }
private static IList<Possibility> GetUnsolvedSquarePossibilities(Grid grid) { var list = new List<Possibility>(); for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { for (var colIdx = 0; colIdx < 9; ++ colIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) continue; list.Add(new Possibility(rowIdx, colIdx, grid.Squares[rowIdx, colIdx].PossibleDigits.ToList())); } } return list; }
public static Grid Clone(this Grid grid) { var clone = new Grid {Squares = new Square[9, 9]}; for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { for (var colIdx = 0; colIdx < 9; ++colIdx) { clone.Squares[rowIdx, colIdx] = new Square(); //if (grid.Squares[rowIdx, colIdx].IsSolved) { clone.Squares[rowIdx, colIdx].SetDigit(grid.Squares[rowIdx, colIdx].Digit); } } } return clone; }
public void Solve(Grid grid) { // Need to have a column after so only check first 8 cols for (var firstColIdx = 0; firstColIdx < 8; ++ firstColIdx) { var firstPossibleLocations = GetPossibleLocations(grid, firstColIdx); // Find digits that have exactly two possible locations - possible rectangle var potentialRectangleLocations = firstPossibleLocations.Where(pl => pl.Value.Count == 2); foreach (var potentialRectangleLocation in potentialRectangleLocations) { var digitToMatch = potentialRectangleLocation.Key; var topRowToMatch = potentialRectangleLocation.Value[0]; var bottomRowToMatch = potentialRectangleLocation.Value[1]; for (var secondColIdx = firstColIdx + 1; secondColIdx < 9; ++ secondColIdx) { var secondPossibleLocations = GetPossibleLocations(grid, secondColIdx); if (!secondPossibleLocations.ContainsKey(digitToMatch) || secondPossibleLocations[digitToMatch].Count != 2) continue; if (!secondPossibleLocations[digitToMatch].Contains(topRowToMatch) || !secondPossibleLocations[digitToMatch].Contains(bottomRowToMatch)) continue; // Have found a rectangle, now remove the digit (if it exists) from any other columns in the top and bottom rows for (var colToRemoveIdx = 0; colToRemoveIdx < 9; ++ colToRemoveIdx) { // Avoid removing it from the rectangle if (colToRemoveIdx == firstColIdx || colToRemoveIdx == secondColIdx) continue; if (!grid.Squares[topRowToMatch, colToRemoveIdx].IsSolved) { grid.CheckIfTestCase(topRowToMatch, colToRemoveIdx, digitToMatch); grid.Squares[topRowToMatch, colToRemoveIdx].RemovePossibleDigit(digitToMatch); } if (!grid.Squares[bottomRowToMatch, colToRemoveIdx].IsSolved) { grid.CheckIfTestCase(bottomRowToMatch, colToRemoveIdx, digitToMatch); grid.Squares[bottomRowToMatch, colToRemoveIdx].RemovePossibleDigit(digitToMatch); } } } } } }
private static Dictionary<int, List<int>> GetPossibleLocations(Grid grid, int colIdx) { var possibleLocations = new Dictionary<int, List<int>>(); for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { var square = grid.Squares[rowIdx, colIdx]; if (square.IsSolved) continue; foreach (var possibleDigit in square.PossibleDigits) { if (!possibleLocations.ContainsKey(possibleDigit)) { possibleLocations.Add(possibleDigit, new List<int>()); } possibleLocations[possibleDigit].Add(rowIdx); } } return possibleLocations; }
private static void NakedPairByRow(Grid grid) { for (var rowIdx = 0; rowIdx < 9; ++rowIdx) { var possibilitiesInRow = new Dictionary<int, List<int>>(); for (var colIdx = 0; colIdx < 9; ++colIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) continue; var listOfPossibilitiesForSquare = grid.Squares[rowIdx, colIdx].PossibleDigits.ToList(); if (listOfPossibilitiesForSquare.Count != 2) continue; possibilitiesInRow.Add(colIdx, listOfPossibilitiesForSquare); } if (possibilitiesInRow.Count < 2) continue; foreach (var squareWithPairOfPossibilities in possibilitiesInRow) { var firstDigit = squareWithPairOfPossibilities.Value[0]; var secondDigit = squareWithPairOfPossibilities.Value[1]; foreach (var anotherSquareWithPairOfPossibilities in possibilitiesInRow) { if (anotherSquareWithPairOfPossibilities.Key == squareWithPairOfPossibilities.Key) continue; if (!anotherSquareWithPairOfPossibilities.Value.Contains(firstDigit)) continue; if (!anotherSquareWithPairOfPossibilities.Value.Contains(secondDigit)) continue; // Found two squares that share the same two possibilities - so remove from other squares in the row for (var colIdx = 0; colIdx < 9; ++ colIdx) { if (colIdx == squareWithPairOfPossibilities.Key) continue; if (colIdx == anotherSquareWithPairOfPossibilities.Key) continue; if (grid.Squares[rowIdx, colIdx].IsSolved) continue; grid.Squares[rowIdx, colIdx].RemovePossibleDigit(firstDigit); grid.Squares[rowIdx, colIdx].RemovePossibleDigit(secondDigit); } } } } }
public void Solve(Grid grid) { var boxCols = grid.GetBoxColumns(); var boxRows = grid.GetBoxRows(); for (var digit = 1; digit <= 9; ++digit) { for (var boxRow = 1; boxRow <= 3; ++boxRow) { var topRow = boxRows[boxRow].Item1; var middleRow = boxRows[boxRow].Item1 + 1; var bottomRow = boxRows[boxRow].Item2; var possibleDigitRows = grid.FindThePotentialDigitInTheBoxRows(boxCols, digit, topRow, middleRow, bottomRow); if (possibleDigitRows.Values.All(v => v.Count != 1)) continue; foreach (var possibleDigitRow in possibleDigitRows) { if (possibleDigitRow.Value.Count != 1) continue; var rowToClear = possibleDigitRow.Value[0]; for (var otherBoxCol = 1; otherBoxCol <= 3; ++otherBoxCol) { if (otherBoxCol == possibleDigitRow.Key) continue; for (var colIdx = boxCols[otherBoxCol].Item1; colIdx <= boxCols[otherBoxCol].Item2; ++colIdx) { if (grid.Squares[rowToClear, colIdx].IsSolved) continue; grid.Squares[rowToClear, colIdx].RemovePossibleDigit(digit); } } } } } }
private void SolveForSquare(Grid grid, int firstRow, int lastRow, int firstCol, int lastCol) { var fixedDigits = new HashSet<int>(); for (var rowIdx = firstRow; rowIdx <= lastRow; ++rowIdx) { for (var colIdx = firstCol; colIdx <= lastCol; ++colIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) { fixedDigits.Add(grid.Squares[rowIdx, colIdx].Digit); } } } for (var digitToPlace = 1; digitToPlace <= 9; ++digitToPlace) { var possibleLocations = new List<Tuple<int, int>>(); if (fixedDigits.Contains(digitToPlace)) continue; for (var rowIdx = firstRow; rowIdx <= lastRow; ++rowIdx) { for (var colIdx = firstCol; colIdx <= lastCol; ++colIdx) { if (grid.Squares[rowIdx, colIdx].IsSolved) continue; if (grid.Squares[rowIdx, colIdx].PossibleDigits.Contains(digitToPlace)) { possibleLocations.Add(new Tuple<int, int>(rowIdx, colIdx)); } } } if (possibleLocations.Count == 1) { var location = possibleLocations[0]; grid.SetDigit(location.Item1, location.Item2, digitToPlace); } } }
public void Solve(Grid grid) { var boxCols = grid.GetBoxColumns(); var boxRows = grid.GetBoxRows(); for (var digit = 1; digit <= 9; ++digit) { for (var boxCol = 1; boxCol <= 3; ++boxCol) { var leftColumn = boxCols[boxCol].Item1; var middleColumn = boxCols[boxCol].Item1 + 1; var rightColumn = boxCols[boxCol].Item2; var possibleDigitBoxColumns = grid.FindThePotentialDigitInTheBoxColumns(boxRows, digit, leftColumn, middleColumn, rightColumn); if (possibleDigitBoxColumns.Values.All(v => v.Count != 1)) continue; // Where there is only one possible column for a digit within the box, remove it from that column for other boxes foreach (var possibleDigitBoxColumn in possibleDigitBoxColumns) { if (possibleDigitBoxColumn.Value.Count != 1) continue; var columnToClear = possibleDigitBoxColumn.Value[0]; for (var otherBoxRow = 1; otherBoxRow <= 3; ++otherBoxRow) { if (otherBoxRow == possibleDigitBoxColumn.Key) continue; for (var rowIdx = boxRows[otherBoxRow].Item1; rowIdx <= boxRows[otherBoxRow].Item2; ++rowIdx) { if (grid.Squares[rowIdx, columnToClear].IsSolved) continue; grid.Squares[rowIdx, columnToClear].RemovePossibleDigit(digit); } } } } } }
public void Solve(Grid grid) { NakedPairByRow(grid); }
public void Solve(Grid grid) { const int IgnoreColumn = -1; var boxRows = grid.GetBoxRows(); var boxCols = grid.GetBoxColumns(); for (var digit = 1; digit <= 9; ++digit) { for (var boxCol = 1; boxCol <= 3; ++boxCol) { var leftColumn = boxCols[boxCol].Item1; var middleColumn = boxCols[boxCol].Item1 + 1; var rightColumn = boxCols[boxCol].Item2; var possibleDigitBoxColumns = grid.FindThePotentialDigitInTheBoxColumns(boxRows, digit, leftColumn, middleColumn, rightColumn); var countOfColumnBoxPairs = possibleDigitBoxColumns.Count(pb => pb.Value.Count == 2); if (countOfColumnBoxPairs < 2) continue; foreach (var possibleDigitBoxColumn in possibleDigitBoxColumns) { var firstBox = possibleDigitBoxColumn.Key; if (possibleDigitBoxColumn.Value.Count == 0) continue; if (possibleDigitBoxColumn.Value.Count > 2) continue; var firstColumn = possibleDigitBoxColumn.Value[0]; var secondColumn = possibleDigitBoxColumn.Value.Count > 1 ? possibleDigitBoxColumn.Value[1] : IgnoreColumn; foreach (var otherDigitBoxColumn in possibleDigitBoxColumns) { if (otherDigitBoxColumn.Key == possibleDigitBoxColumn.Key) continue; if (otherDigitBoxColumn.Value.Count > 2) continue; if (otherDigitBoxColumn.Value.Count == 0) continue; if (!otherDigitBoxColumn.Value.Contains(firstColumn)) continue; if (secondColumn != IgnoreColumn && !otherDigitBoxColumn.Value.Contains(secondColumn)) continue; var secondBox = otherDigitBoxColumn.Key; if (secondColumn == IgnoreColumn && otherDigitBoxColumn.Value.Count > 1) { secondColumn = otherDigitBoxColumn.Value[1]; } // Digit is restricted to two particular columns between two boxes // Therefore remove from the third var thirdBox = possibleDigitBoxColumns.Single(pb => pb.Key != firstBox && pb.Key != secondBox); for (var rowIdx = boxRows[thirdBox.Key].Item1; rowIdx <= boxRows[thirdBox.Key].Item2; ++ rowIdx) { grid.Squares[rowIdx, firstColumn].RemovePossibleDigit(digit); if (secondColumn != IgnoreColumn) { grid.Squares[rowIdx, secondColumn].RemovePossibleDigit(digit); } } } } } } }
public void ConfirmBoxEndPosition(int startIdx, int expectedEndIdx) { var grid = new Grid(); var endPosition = grid.GetBoxEnd(startIdx); endPosition.Should().Be(expectedEndIdx); }
/// <summary> /// Given that you need to place a certain digit in two of the three boxes in a row (i.e. it has already been placed in one box) /// Can you eliminate one of the rows because all three squares within a particular box have been filled with other digits? /// /// e.g. /// 100 920 000 /// 524 017 009 /// 000 004 271 /// /// - in this the digit 5 has only been positioned once (the middle row). So theoretically in the other two boxes /// it will appear in either the top or the bottom row. However it can't appear in the bottom row in the third box /// because all three squares have been taken. Therefore despite not knowing which of the top row squares in the third box /// it will appear in, you know that it can't appear in the top row in the middle box and therefore it must be in /// the bottom row for the middle box /// i.e. the only remaining possibilities for 5 are marked as ? and it can be removed as a possibility from the square X /// 100 92X ??? /// 524 017 009 /// 000 ??4 271 /// </summary> /// <param name="grid"></param> public void Solve(Grid grid) { var boxCols = grid.GetBoxColumns(); var boxRows = grid.GetBoxRows(); for (var digit = 1; digit <= 9; ++digit) { // Top box row then middle box row then bottom box row (of 3x3 squares) for (var boxRow = 1; boxRow <= 3; ++boxRow) { // Square rows within the box row var topSquareRow = boxRows[boxRow].Item1; var middleSquareRow = boxRows[boxRow].Item1 + 1; var bottomSquareRow = boxRows[boxRow].Item2; // Key is the box number (i.e. left, middle, right) and value is the row // in which the digit is found (if at all) var rowInBox = grid.FindTheDigitWithinEachBoxInTheBoxRow(boxCols, digit, topSquareRow, middleSquareRow, bottomSquareRow); // If the current digit has been placed in exactly one box in the current box row // then there is potential to eliminate it as a possibility from one of the square rows // in the remaining two boxes var numberOfBoxesPlaced = rowInBox.Count(kvp => kvp.Value != GridDigitInBoxExtensions.NotFound); if (numberOfBoxesPlaced != 1) continue; var digitPlacedBoxCombo = rowInBox.First(kvp => kvp.Value != GridDigitInBoxExtensions.NotFound); var boxWhereDigitPlaced = digitPlacedBoxCombo.Key; var digitPlacedInRow = digitPlacedBoxCombo.Value; int firstRowToCheck; int secondRowToCheck; if (digitPlacedInRow == topSquareRow) { firstRowToCheck = middleSquareRow; secondRowToCheck = bottomSquareRow; } else if (digitPlacedInRow == middleSquareRow) { firstRowToCheck = topSquareRow; secondRowToCheck = bottomSquareRow; } else { firstRowToCheck = topSquareRow; secondRowToCheck = middleSquareRow; } // Key is box number, value is whether or not all squares in that var completeFirstRowInBox = new Dictionary<int, int> {{1, 0}, {2, 0}, {3, 0}}; var completeSecondRowInBox = new Dictionary<int, int> { { 1, 0 }, { 2, 0 }, { 3, 0 } }; for (var boxCol = 1; boxCol <= 3; ++boxCol) { if (rowInBox[boxCol] != GridDigitInBoxExtensions.NotFound) { continue; } var firstRowSolvedSquares = 0; var secondRowSolvedSquares = 0; for (var colIdx = boxCols[boxCol].Item1; colIdx <= boxCols[boxCol].Item2; ++colIdx) { if (grid.Squares[firstRowToCheck, colIdx].IsSolved) { firstRowSolvedSquares++; } if (grid.Squares[secondRowToCheck, colIdx].IsSolved) { secondRowSolvedSquares++; } } if (firstRowSolvedSquares == 3) { completeFirstRowInBox[boxCol] = boxCol; } if (secondRowSolvedSquares == 3) { completeSecondRowInBox[boxCol] = boxCol; } } var boxToClear = 0; var rowToClear = 0; // If there is any row within a box (unsolved for the digit) that is complete // then there is no room for the digit and then for that row it *must* be in the third box if (completeFirstRowInBox.Any(kvp => kvp.Value != 0)) { rowToClear = secondRowToCheck; var boxWhereComplete = completeFirstRowInBox.First(kvp => kvp.Value != 0).Key; boxToClear = FindBoxToClear(boxWhereDigitPlaced, boxWhereComplete); } if (completeSecondRowInBox.Any(kvp => kvp.Value != 0)) { rowToClear = firstRowToCheck; var boxWhereComplete = completeSecondRowInBox.First(kvp => kvp.Value != 0).Key; boxToClear = FindBoxToClear(boxWhereDigitPlaced, boxWhereComplete); } if (boxToClear != 0) { for (var colIdx = boxCols[boxToClear].Item1; colIdx <= boxCols[boxToClear].Item2; ++colIdx) { grid.CheckIfTestCase(rowToClear, colIdx, digit); grid.Squares[rowToClear, colIdx].RemovePossibleDigit(digit); } } } } }
public void ConfirmBoxStartPosition(int idx, int expectedStart) { var grid = new Grid(); var startPosition = grid.GetBoxStart(idx); startPosition.Should().Be(expectedStart); }