public static void DrawPuzzle(Puzzle puzzle, Solution solution) { PrintColumnSolution(solution); for (int i = 0; i < 9; i++) { var row = puzzle.GetRow(i); if (i == 3 || i == 6) { WriteLine("------+-------+------"); } for (int j = 0; j < 9; j++) { if (j == 3 || j == 6) { Write($"| {row.Segment[j]} "); } else if (j == 8) { Write($"{row.Segment[j]}"); } else { Write($"{row.Segment[j]} "); } } if (solution.Solved && i == solution.Row) { Write("*"); } WriteLine(); } WriteLine(puzzle); void PrintColumnSolution(Solution solution) { if (!solution.Solved) { return; } for (int i = 0; i < solution.Column; i++) { Write(" "); if (i == 2 || i == 5) { Write(" "); } } Write("*"); WriteLine(); } }
public bool TrySolveRow(int index, out Solution solution) { if (_puzzle.SolvedForRow[index] == _effectiveCount && TrySolveLine(_puzzle.GetRow(index), out int column, out int value)) { solution = new Solution( index, column, value, nameof(NakedSinglesSolver), null ); return(true); } solution = null; return(false); }
public Solution SolveRow(int index) { var line = _puzzle.GetRow(index); (bool solved, int cell, int value) = SolveLine(line); var solution = new Solution { Solved = solved }; if (solved) { solution.Column = cell; solution.Row = index; solution.Value = value; solution.Solver = this; return(solution); } return(solution); }
public Solution Solve(int index) { var box = _puzzle.GetBox(index); // adjacent neighboring boxes // first two values in array are horizontal neighbors // second two values in array are vertical neighbors var offset = (index / 3) * 3; int[] avn = new int[] { (index + 1) % 3 + offset, (index + 2) % 3 + offset, (index + 3) % 9, (index + 6) % 9 }; // get adjacent neighboring boxes var ahnb1 = _puzzle.GetBox(avn[0]); var ahnb2 = _puzzle.GetBox(avn[1]); var avnb1 = _puzzle.GetBox(avn[2]); var avnb2 = _puzzle.GetBox(avn[3]); // iterate over the three rows in the box for (int i = 0; i < 3; i++) { // for each row, the neighboring rows are the same // the columns differ per cell var row1Index = i; var row2Index = (i + 1) % 3; var row3Index = (i + 2) % 3; // there are nine row to consider (three boxes) // baseline box var row2 = box.GetRow(row2Index); var row3 = box.GetRow(row3Index); // horizontal adjacent box 1 -- rows var ahnb1Row2 = ahnb1.GetRow(row2Index); var ahnb1Row3 = ahnb1.GetRow(row3Index); // horizontal adjacent box 2 -- rows var ahnb2Row2 = ahnb2.GetRow(row2Index); var ahnb2Row3 = ahnb2.GetRow(row3Index); // determine union of values of rows var row2Union = ahnb1Row2.Union(ahnb2Row2); var row3Union = ahnb1Row3.Union(ahnb2Row3); // get complete row that includes the the first row of box var firstRowIndex = box.GetRowOffsetForBox() + i; var firstRow = _puzzle.GetRow(firstRowIndex); // the boundaries of the adjacent boxes don't matter // the appropriate rows have been merged (Union) at this point // determine union of values of row 1 -- these values are all off-limits // get complete row that includes the the first row of box var boxValues = box.AsValues(); // determine disjoint set with baseline rows -- box row values are off-limits // determine disjoint set with baseline rows -- box row values are off-limits var row2Values = row2Union.DisjointSet(boxValues); var row3Values = row3Union.DisjointSet(boxValues); // determine disjoint set with baseline row -- looking for values now in that row var row2Candidates = row2Values.DisjointSet(firstRow.Segment); var row3Candidates = row3Values.DisjointSet(firstRow.Segment); // row candidates -- values in both row 2 and 3 but not in row 1 or in the box ReadOnlySpan <int> rowCandidates = new int[] {}; if (row2[0] == 0 && row3[0] == 0) { rowCandidates = row2Candidates.Intersect(row3Candidates); } else if (row2[0] == 0) { rowCandidates = row2Candidates; } else if (row3[0] == 0) { rowCandidates = row3Candidates; } if (rowCandidates.Length == 0) { continue; } // cells for (int y = 0; y < 3; y++) { var col1Index = y; var col2Index = (y + 1) % 3; var col3Index = (y + 2) % 3; // baseline box var col1 = box.GetColumn(col1Index); var col2 = box.GetColumn(col2Index); var col3 = box.GetColumn(col3Index); // vertical adjacent box 1 -- cols var avnb1Col1 = avnb1.GetColumn(col1Index); var avnb1Col2 = avnb1.GetColumn(col2Index); var avnb1Col3 = avnb1.GetColumn(col3Index); // vertical adjacent box 2 -- cols var avnb2Col1 = avnb2.GetColumn(col1Index); var avnb2Col2 = avnb2.GetColumn(col2Index); var avnb2Col3 = avnb2.GetColumn(col3Index); // get complete column that includes the the first column of box var firstColIndex = box.GetColumnOffsetForBox() + y; var firstCol = _puzzle.GetColumn(firstColIndex); // determine union of values of cols var col2Union = avnb1Col2.Union(avnb2Col2); var col3Union = avnb1Col3.Union(avnb2Col3); // determine disjoint set with baseline cols -- box col values are off-limits var col2Values = col2Union.DisjointSet(boxValues); var col3Values = col3Union.DisjointSet(boxValues); // determine disjoint set with baseline col -- looking for values now in that col var col2Candidates = col2Values.DisjointSet(firstCol.Segment); var col3Candidates = col3Values.DisjointSet(firstCol.Segment); ReadOnlySpan <int> colCandidates = new int[] { }; if (col2[0] == 0 && col3[0] == 0) { colCandidates = col2Candidates.Intersect(col3Candidates); } else if (col2[0] == 0) { colCandidates = col2Candidates; } else if (col3[0] == 0) { colCandidates = col3Candidates; } // col candidates -- values in both col 2 and 3 but not in col 1 or in the box //var colCandidates = col2Candidates.Intersect(col3Candidates); if (colCandidates.Length == 0) { continue; } var candidates = colCandidates.Intersect(rowCandidates); if (candidates.Length == 1) { (var row, var column) = Puzzle.GetLocationForBoxCell(index, (i * 3) + y); return(new Solution { Solved = true, Value = candidates[0], Row = row, Column = column, Solver = this }); } } } return(new Solution { Solved = false }); }
public Solution Solve(int index) { var box = _puzzle.GetBox(index); // adjacent neighboring boxes // first two values in array are horizontal neighbors // second two values in array are vertical neighbors var offset = (index / 3) * 3; int[] avn = new int[] { (index + 1) % 3 + offset, (index + 2) % 3 + offset, (index + 3) % 9, (index + 6) % 9 }; // get adjacent neighboring boxes var ahnb1 = _puzzle.GetBox(avn[0]); var ahnb2 = _puzzle.GetBox(avn[1]); var avnb1 = _puzzle.GetBox(avn[2]); var avnb2 = _puzzle.GetBox(avn[3]); // iterate over the three rows in the box // goal is to solve a cell in this row (not other rows) // ititial logic (body of first for loop) can solve any row cell using // horizontal adjacent rows as input // later logic (body of embedded for loop) can solve a given cell, one at a time // using rows and/or columns as input for (int i = 0; i < 3; i++) { // calculate rows for i var row1Index = i; var row2Index = (i + 1) % 3; var row3Index = (i + 2) % 3; // target box var boxRow1 = box.GetRow(row1Index); if (!boxRow1.ContainsValue(0)) { continue; } var boxRow2 = box.GetRow(row2Index); var boxRow3 = box.GetRow(row3Index); // neighboring boxess // horizontal adjacent box 1 -- rows var ahnb1Row1 = ahnb1.GetRow(row1Index); var ahnb1Row2 = ahnb1.GetRow(row2Index); var ahnb1Row3 = ahnb1.GetRow(row3Index); // horizontal adjacent box 2 -- rows var ahnb2Row1 = ahnb1.GetRow(row1Index); var ahnb2Row2 = ahnb2.GetRow(row2Index); var ahnb2Row3 = ahnb2.GetRow(row3Index); // determine union of values of rows var adjRow2Union = ahnb1Row2.Union(ahnb2Row2); var adjRow3Union = ahnb1Row3.Union(ahnb2Row3); // get complete row that includes current row of box var currentRowIndex = box.GetRowOffsetForBox() + i; //TODO: consider an overload that only returns non-zero values var currentRow = _puzzle.GetRow(currentRowIndex); // get all values in box var boxLine = box.AsLine(); //var boxValues = box.AsValues(); // calculate full set of illegal values for row 1 var boxRow1IllegalValues = boxLine.Union(currentRow); // determine disjoint set with baseline row -- looking for values in that row var row2Candidates = adjRow2Union.DisjointSet(boxRow1IllegalValues); var row3Candidates = adjRow3Union.DisjointSet(boxRow1IllegalValues); var boxRow1Candidates = row2Candidates.Intersect(row3Candidates); // determine if rows on their own present a solution if (boxRow1Candidates.Length == 1) { (var justOne, var column) = boxRow1.IsJustOneElementUnsolved(); if (justOne) { var solverKind = nameof(Strategy.RowSolver); return(GetSolution(index, i, column, boxRow1Candidates[0], solverKind)); } } // interate over columns for row // mostly targets row[column] cell for (int y = 0; y < 3; y++) { if (boxRow1[y] != 0) { continue; } var col1Index = y; var col2Index = (y + 1) % 3; var col3Index = (y + 2) % 3; // baseline box var boxCol1 = box.GetColumn(col1Index); var boxCol2 = box.GetColumn(col2Index); var boxCol3 = box.GetColumn(col3Index); // vertical adjacent box 1 -- cols var avnb1Col1 = avnb1.GetColumn(col1Index); var avnb1Col2 = avnb1.GetColumn(col2Index); var avnb1Col3 = avnb1.GetColumn(col3Index); // vertical adjacent box 2 -- cols var avnb2Col1 = avnb2.GetColumn(col1Index); var avnb2Col2 = avnb2.GetColumn(col2Index); var avnb2Col3 = avnb2.GetColumn(col3Index); // get complete column that includes the the first column of box var currentColIndex = box.GetColumnOffsetForBox() + y; var currentCol = _puzzle.GetColumn(currentColIndex); // calculate full set of illegal values for col 1 var boxCol1IllegalValues = boxLine.Union(currentCol); // determine union of values of cols var col2Union = avnb1Col2.Union(avnb2Col2); var col3Union = avnb1Col3.Union(avnb2Col3); // determine disjoint set with baseline col -- looking for values now in that col var col2Candidates = col2Union.DisjointSet(boxCol1IllegalValues); var col3Candidates = col3Union.DisjointSet(boxCol1IllegalValues); var boxCol1Candidates = col2Candidates.Intersect(col3Candidates); // determine if columns on their own present a solution // can solve any cell in given box column if (boxCol1Candidates.Length == 1) { (var justOne, var row) = boxCol1.IsJustOneElementUnsolved(); if (justOne) { var solverKind = nameof(Strategy.ColumnSolver); return(GetSolution(index, row, y, boxCol1Candidates[0], solverKind)); } } // determine if rows and columns together present a solution // test: SolutionAllSlotsEmpty var rowAndColumnCandidates = boxRow1Candidates.Intersect(boxCol1Candidates); if (rowAndColumnCandidates.Length == 1) { if (boxCol1[i] == 0) { var solverKind = nameof(Strategy.RowColumnSolver); return(GetSolution(index, i, y, rowAndColumnCandidates[0], solverKind)); } } // determine if various combinations of rows and columns present a solution // col1[row] is candidate for all following cases // the following set of cases involve a row or a column with: // a candidate, one cell == 0 and the other non zero // the cases are looking for a set of rows and columns // that project the same value except for the row or column with // the non-zero value (which doesn't require a projected value) // check rows if (boxRow1Candidates.Length > 0) { bool solved; Solution solution; var solverKind1 = nameof(Strategy.Column2CandidateColumn3BlockedRowSolver); // row1[col3] has a value, so col3 don't need to participate in providing a candidate if (col2Candidates.Length > 0 && boxRow1[col2Index] == 0 && boxRow1[col3Index] != 0 && ((solved, solution) = CheckForIntersectionCandidates(boxRow1Candidates, col2Candidates, solverKind1)).solved) { return(solution); } var solverKind2 = nameof(Strategy.Column2BlockedColumn3CandidateRowSolver); // row1[col2] has a value, so col2 don't need to participate in providing a candidate if (col3Candidates.Length > 0 && boxRow1[col2Index] != 0 && boxRow1[col3Index] == 0 && ((solved, solution) = CheckForIntersectionCandidates(boxRow1Candidates, col3Candidates, solverKind2)).solved) { return(solution); } } // check columns if (boxCol1Candidates.Length > 0) { bool solved; Solution solution; var solver1 = nameof(Strategy.Row2CandidateRow3BlockedColumnSolver); // col1[row3] has a value, so row3 don't need to participate in providing a candidate if (row2Candidates.Length > 0 && boxCol1[row2Index] == 0 && boxCol1[row3Index] != 0 && ((solved, solution) = CheckForIntersectionCandidates(boxCol1Candidates, row2Candidates, solver1)).solved) { return(solution); } var solver2 = nameof(Strategy.Row2BlockedRow3CandidateColumnSolver); // col1[row2] has a value, so row2 don't need to participate in providing a candidate // test: SolutionTwoSlotsEmptyInColumn if (row3Candidates.Length > 0 && boxCol1[row2Index] != 0 && boxCol1[row3Index] == 0 && ((solved, solution) = CheckForIntersectionCandidates(boxCol1Candidates, row3Candidates, solver2)).solved) { return(solution); } } // the following set of cases involve a row or column with: // all cells filled, such that a row or column doesn't need to participate in providing a candidate // col3 is blocked, so col3 doesn't need to participate in providing a candidate var col2Blocked = boxCol2.GetUnsolvedCount() == 0; var col3Blocked = boxCol3.GetUnsolvedCount() == 0; var row2Blocked = boxRow2.GetUnsolvedCount() == 0; var row3Blocked = boxRow3.GetUnsolvedCount() == 0; if (boxCol1.GetUnsolvedCount() == 1) { if (col3Blocked && col2Candidates.Length == 1) { var solverKind = nameof(Strategy.Column2CandidateColumn3Blocked); return(GetSolution(index, i, y, col2Candidates[0], solverKind)); } } // following set of cases test hidden singles in columns or rows // pattern // box 1 col1 is full // box 2 has one value not in col1 // only one slot available in col1 in current box // means that current cell must have that value { var solverKind = nameof(Strategy.ColumnLastPossibleSlot); var candidates2 = avnb2.AsLine().DisjointSet(currentCol); foreach (var candidate in candidates2) { if (!boxLine.ContainsValue(candidate) && !currentRow.ContainsValue(candidate) && CheckForValueInCellOrRow(candidate, box, col1Index, row2Index, row3Index) && CheckForValueInCellOrRow(candidate, avnb1, col1Index, 0, 1, 2) ) { solverKind += "-1"; return(GetSolution(index, i, y, candidate, solverKind)); } } var candidates1 = avnb1.AsLine().DisjointSet(currentCol); foreach (var candidate in candidates1) { if (!boxLine.ContainsValue(candidate) && !currentRow.ContainsValue(candidate) && CheckForValueInCellOrRow(candidate, box, col1Index, row2Index, row3Index) && CheckForValueInCellOrRow(candidate, avnb2, col1Index, 0, 1, 2) ) { solverKind += "-2"; return(GetSolution(index, i, y, candidate, solverKind)); } } } /* * bool solved; * Solution solution; * * // start with columns * // test each cell in avnb1.col1 * // avnb2 presents candidate * if (((solved, solution) = CheckForDisjointCandidates(avnb2.AsLine().Segment, currentCol.Segment, solverKind)).solved && * !boxLine.ContainsValue(solution.Value) && * !currentRow.ContainsValue(solution.Value) && * CheckForValueInCellOrRow(solution.Value,box,col1Index,row2Index,row3Index) && * CheckForValueInCellOrRow(solution.Value,avnb1,col1Index,0,1,2) * ) * { * solution.SolverKind += "-1"; * return solution; * } * * // test avnb2.col1 solved * // avnb1 presents candidate * if (((solved, solution) = CheckForDisjointCandidates(avnb1.AsLine().Segment, currentCol.Segment, solverKind)).solved && * !boxLine.ContainsValue(solution.Value) && * !currentRow.Segment.Contains(solution.Value) && * CheckForValueInCellOrRow(solution.Value,box,col1Index,row2Index,row3Index) && * CheckForValueInCellOrRow(solution.Value,avnb2,col1Index,0,1,2) * ) * { * solution.SolverKind += "-2"; * return solution; * } * } */ { var solverKind = nameof(Strategy.RowLastPossibleSlot); var candidates2 = ahnb2.AsLine().DisjointSet(currentRow); foreach (var candidate in candidates2) { if (!boxLine.ContainsValue(candidate) && !currentCol.Segment.Contains(candidate) && CheckForValueInCellOrColumn(candidate, box, row1Index, col2Index, col3Index) && CheckForValueInCellOrColumn(candidate, ahnb1, row1Index, 0, 1, 2) ) { solverKind += "-2"; return(GetSolution(index, i, y, candidate, solverKind)); } } var candidates1 = ahnb1.AsLine().DisjointSet(currentRow); foreach (var candidate in candidates1) { if (!boxLine.ContainsValue(candidate) && !currentCol.Segment.Contains(candidate) && CheckForValueInCellOrColumn(candidate, box, row1Index, col2Index, col3Index) && CheckForValueInCellOrColumn(candidate, ahnb2, row1Index, 0, 1, 2) ) { solverKind += "-2"; return(GetSolution(index, i, y, candidate, solverKind)); } } } /* * bool solved; * Solution solution; * * * * // solve for rows * // test each cell in avhb1.row1 * // avhb2 presents candidate * if (((solved, solution) = CheckForDisjointCandidates(ahnb2.AsLine().Segment, currentRow.Segment, solverKind)).solved && * !boxLine.ContainsValue(solution.Value) && * !currentCol.Segment.Contains(solution.Value) && * CheckForValueInCellOrColumn(solution.Value,box,row1Index,col2Index,col3Index) && * CheckForValueInCellOrColumn(solution.Value,ahnb1,row1Index,0,1,2) * ) * { * solution.SolverKind += "-1"; * return solution; * } * * // test ahnb2.row1 solved * // ahnb1 presents candidate * if (((solved, solution) = CheckForDisjointCandidates(ahnb1.AsLine().Segment, currentRow.Segment, solverKind)).solved && * !boxLine.ContainsValue(solution.Value) && * !currentCol.Segment.Contains(solution.Value) && * CheckForValueInCellOrColumn(solution.Value,box,row1Index,col2Index,col3Index) && * CheckForValueInCellOrColumn(solution.Value,ahnb2,row1Index,0,1,2) * ) * { * solution.SolverKind += "-2"; * return solution; * } * } */ // Strategy where a column or row has two empty cells and one is constrained { var solverKind = nameof(Strategy.ColumnLastTwoPossibleSlots); if (currentCol.GetUnsolvedCount() == 2) { var missingValues = currentCol.GetMissingValues(); for (int k = 0; k < 2; k++) { var value = missingValues[k]; var otherValue = missingValues[(k + 1) % 2]; if (currentRow.ContainsValue(value) && !boxLine.ContainsValue(otherValue)) { return(GetSolution(index, i, y, otherValue, solverKind)); } } } } { var solverKind = nameof(Strategy.RowLastTwoPossibleSlots); if (currentRow.GetUnsolvedCount() == 2) { var missingValues = currentRow.GetMissingValues(); for (int k = 0; k < 2; k++) { var value = missingValues[k]; var otherValue = missingValues[(k + 1) % 2]; if (currentCol.ContainsValue(value) && !boxLine.ContainsValue(otherValue)) { return(GetSolution(index, i, y, otherValue, solverKind)); } } } } bool CheckRowForValue(int searchValue, Box box, int row) { // get complete row that includes current row of box var rowIndex = box.GetRowOffsetForBox() + row; //TODO: consider an overload that only returns non-zero values var targetRow = _puzzle.GetRow(rowIndex); return(targetRow.Segment.Contains(searchValue)); } bool CheckForValueInCellOrColumn(int value, Box box, int row, params int[] cols) { foreach (int col in cols) { var targetColumn = box.GetColumn(col); if (targetColumn[row] != 0 || CheckColumnForValue(value, box, col)) { } else { return(false); } } return(true); } bool CheckForValueInCellOrRow(int value, Box box, int col, params int[] rows) { foreach (int row in rows) { var targetRow = box.GetRow(row); if (targetRow[col] != 0 || CheckRowForValue(value, box, row)) { } else { return(false); } } return(true); } bool CheckColumnForValue(int searchValue, Box box, int col) { // get complete column that includes current column of box var colIndex = box.GetColumnOffsetForBox() + col; //TODO: consider an overload that only returns non-zero values var targetCol = _puzzle.GetColumn(colIndex); return(targetCol.Segment.Contains(searchValue)); } (bool solved, Solution solution) CheckForIntersectionCandidates(ReadOnlySpan <int> candidate1, ReadOnlySpan <int> candidate2, string solverKind) { var candidates = candidate1.Intersect(candidate2); if (candidates.Length == 1) { return(true, GetSolution(index, i, y, candidates[0], solverKind)); } return(false, Solution.False); } (bool solved, Solution solution) CheckForDisjointCandidates(ReadOnlySpan <int> line, ReadOnlySpan <int> candidate2, string solverKind) { var candidates = line.DisjointSet(candidate2); if (candidates.Length == 1) { return(true, GetSolution(index, i, y, candidates[0], solverKind)); } return(false, Solution.False); } } Solution GetSolution(int box, int row, int column, int value, string solverKind) { (var r, var c) = Puzzle.GetLocationForBoxCell(index, (row * 3) + column); return(new Solution { Solved = true, Value = value, Row = r, Column = c, Solver = this, SolverKind = solverKind }); } } return(new Solution { Solved = false }); }
// find a box row/column with just one missing value // find a value that is present in the other two rows/columns but not this box // the value can be placed in the open spot if everything lines up public Solution Solve(int index) { var box = _puzzle.GetBox(index); // adjacent neighboring boxes // first two values in array are horizontal neighbors // second two values in array are vertical neighbors var offset = (index / 3) * 3; int[] avn = new int[] { (index + 1) % 3 + offset, (index + 2) % 3 + offset, (index + 3) % 9, (index + 6) % 9 }; var ahnb1 = _puzzle.GetBox(avn[0]); var ahnb2 = _puzzle.GetBox(avn[1]); var avnb1 = _puzzle.GetBox(avn[2]); var avnb2 = _puzzle.GetBox(avn[3]); // find intersection of values for adjacent rows for (int i = 0; i < 3; i++) { (var rowJustOne, var rowCandidateIndex) = box.GetRow(i).IsJustOneElementUnsolved(); if (!rowJustOne) { continue; } // calculate indexes of adjacent rows var row2Index = (i + 1) % 3; var row3Index = (i + 2) % 3; // baseline box var row2 = box.GetRow(row2Index); var row3 = box.GetRow(row3Index); // horizontal adjacent box 1 -- rows var ahnb1Row2 = ahnb1.GetRow(row2Index); var ahnb1Row3 = ahnb1.GetRow(row3Index); // horizontal adjacent box 2 -- rows var ahnb2Row2 = ahnb2.GetRow(row2Index); var ahnb2Row3 = ahnb2.GetRow(row3Index); // get complete row that includes the the first row of box var firstRowIndex = box.GetRowOffsetForBox() + i; var firstRow = _puzzle.GetRow(firstRowIndex); // determine union of values of rows var row2Union = ahnb1Row2.Union(ahnb2Row2); var row3Union = ahnb1Row3.Union(ahnb2Row3); // the boundaries of the adjacent boxes don't matter // the appropriate rows have been merged (Union) at this point // determine union of values of row 1 -- these values are all off-limits // determine disjoint set with baseline rows -- box row values are off-limits var row2Values = row2Union.DisjointSet(row2.Segment); var row3Values = row3Union.DisjointSet(row3.Segment); // determine disjoint set with baseline row -- looking for values now in that row var row2Candidates = row2Values.DisjointSet(firstRow.Segment); var row3Candidates = row3Values.DisjointSet(firstRow.Segment); // row candidates -- values in both row 2 and 3 but not in row 1 or in the box var rowCandidates = row2Candidates.Intersect(row3Candidates); if (rowCandidates.Length == 1) { (var r, var c) = Puzzle.GetLocationForBoxCell(index, i * 3 + rowCandidateIndex); return(new Solution { Solved = true, Row = r, Column = c, Value = rowCandidates[0], Solver = this }); } // find intersection of values for adjacent columns (var colJustOne, var candidateIndex) = box.GetColumn(i).IsJustOneElementUnsolved(); if (!colJustOne) { continue; } var col1Index = i; var col2Index = (i + 1) % 3; var col3Index = (i + 2) % 3; // baseline box var col1 = box.GetColumn(col1Index); var col2 = box.GetColumn(col2Index); var col3 = box.GetColumn(col3Index); // vertical adjacent box 1 -- cols var avnb1Col1 = avnb1.GetColumn(col1Index); var avnb1Col2 = avnb1.GetColumn(col2Index); var avnb1Col3 = avnb1.GetColumn(col3Index); // vertical adjacent box 2 -- cols var avnb2Col1 = avnb2.GetColumn(col1Index); var avnb2Col2 = avnb2.GetColumn(col2Index); var avnb2Col3 = avnb2.GetColumn(col3Index); // get complete column that includes the the first column of box var firstColIndex = box.GetColumnOffsetForBox() + i; var firstCol = _puzzle.GetColumn(firstColIndex); // determine union of values of cols var col2Union = avnb1Col2.Union(avnb2Col2); var col3Union = avnb1Col3.Union(avnb2Col3); // determine disjoint set with baseline cols -- box col values are off-limits var col2Values = col2Union.DisjointSet(col2.Segment); var col3Values = col3Union.DisjointSet(col3.Segment); // determine disjoint set with baseline col -- looking for values now in that col var col2Candidates = col2Values.DisjointSet(firstCol.Segment); var col3Candidates = col3Values.DisjointSet(firstCol.Segment); // col candidates -- values in both col 2 and 3 but not in col 1 or in the box var colCandidates = col2Candidates.Intersect(col3Candidates); if (colCandidates.Length == 1) { (var r, var c) = Puzzle.GetLocationForBoxCell(index, (candidateIndex % 3) * 3 + i); return(new Solution { Solved = true, Row = r, Column = c, Value = colCandidates[0], Solver = this }); } } return(new Solution { Solved = false }); }
public bool TrySolveBox(int index, out Solution solution) { Box box = _puzzle.GetBox(index); int rowOffset = box.GetRowOffset(); int columnOffset = box.GetColumnOffset(); // get adjacent neighboring boxes Box ahnb1 = box.FirstHorizontalNeighbor; Box ahnb2 = box.SecondHorizontalNeighbor; Box avnb1 = box.FirstVerticalNeighbor; Box avnb2 = box.SecondVerticalNeighbor; // iterate over the three rows in the box // goal is to solve a cell in the "i" row // initial logic (body of first for loop) can solve any row cell using // horizontal adjacent rows as input // later logic (body of embedded for loop) can solve a given cell, one at a time // using rows and/or columns as input for (int i = 0; i < 3; i++) { // target box Line boxRow1 = box.GetRow(i); // check if row contains a zero if (!boxRow1.ContainsValue(0)) { continue; } // calculate rows for i int row1Index = i; int row2Index = (i + 1) % 3; int row3Index = (i + 2) % 3; Line boxRow2 = box.GetRow(row2Index); Line boxRow3 = box.GetRow(row3Index); // get all values in box Line boxLine = box.AsLine(); // neighboring boxes // horizontal adjacent box 1 -- rows Line ahnb1Row1 = ahnb1.GetRow(row1Index); Line ahnb1Row2 = ahnb1.GetRow(row2Index); Line ahnb1Row3 = ahnb1.GetRow(row3Index); // horizontal adjacent box 2 -- rows Line ahnb2Row1 = ahnb1.GetRow(row1Index); Line ahnb2Row2 = ahnb2.GetRow(row2Index); Line ahnb2Row3 = ahnb2.GetRow(row3Index); // determine union of values of rows ReadOnlySpan <int> adjRow2Union = ahnb1Row2.Union(ahnb2Row2); ReadOnlySpan <int> adjRow3Union = ahnb1Row3.Union(ahnb2Row3); // get complete row that includes current row of box int currentRowIndex = rowOffset + i; Line currentRow = _puzzle.GetRow(currentRowIndex); // calculate full set of illegal values for row 1 ReadOnlySpan <int> boxRow1IllegalValues = boxLine.Union(currentRow); // determine disjoint set with baseline row -- looking for values in that row ReadOnlySpan <int> row2Candidates = adjRow2Union.DisjointSet(boxRow1IllegalValues); ReadOnlySpan <int> row3Candidates = adjRow3Union.DisjointSet(boxRow1IllegalValues); ReadOnlySpan <int> boxRow1Candidates = row2Candidates.Intersect(row3Candidates); // determine if rows on their own present a solution if (boxRow1Candidates.Length == 1) { if (boxRow1.IsJustOneElementUnsolved(out int column)) { solution = GetSolution(index, i, column, boxRow1Candidates[0], nameof(Strategy.RowSolver)); return(true); } } // iterate over columns for row // mostly targets row[column] cell for (int y = 0; y < 3; y++) { // check if current value is 0 if (boxRow1[y] != 0) { continue; } int col1Index = y; int col2Index = (y + 1) % 3; int col3Index = (y + 2) % 3; // baseline box Line boxCol1 = box.GetColumn(col1Index); Line boxCol2 = box.GetColumn(col2Index); Line boxCol3 = box.GetColumn(col3Index); // vertical adjacent box 1 -- cols Line avnb1Col1 = avnb1.GetColumn(col1Index); Line avnb1Col2 = avnb1.GetColumn(col2Index); Line avnb1Col3 = avnb1.GetColumn(col3Index); // vertical adjacent box 2 -- cols Line avnb2Col1 = avnb2.GetColumn(col1Index); Line avnb2Col2 = avnb2.GetColumn(col2Index); Line avnb2Col3 = avnb2.GetColumn(col3Index); // get complete column that includes the the first column of box int currentColIndex = columnOffset + y; Line currentCol = _puzzle.GetColumn(currentColIndex); // calculate full set of illegal values for col 1 ReadOnlySpan <int> boxCol1IllegalValues = boxLine.Union(currentCol); // determine union of values of cols ReadOnlySpan <int> col2Union = avnb1Col2.Union(avnb2Col2); ReadOnlySpan <int> col3Union = avnb1Col3.Union(avnb2Col3); // determine disjoint set with baseline col -- looking for values now in that col ReadOnlySpan <int> col2Candidates = col2Union.DisjointSet(boxCol1IllegalValues); ReadOnlySpan <int> col3Candidates = col3Union.DisjointSet(boxCol1IllegalValues); ReadOnlySpan <int> boxCol1Candidates = col2Candidates.Intersect(col3Candidates); // determine if columns on their own present a solution // can solve any cell in given box column if (boxCol1Candidates.Length == 1) { if (boxCol1.IsJustOneElementUnsolved(out int row)) { solution = GetSolution(index, row, y, boxCol1Candidates[0], nameof(Strategy.ColumnSolver)); return(true); } } // determine if rows and columns together present a solution // test: SolutionAllSlotsEmpty ReadOnlySpan <int> rowAndColumnCandidates = boxRow1Candidates.Intersect(boxCol1Candidates); if (rowAndColumnCandidates.Length == 1) { if (boxCol1[i] == 0) { solution = GetSolution(index, i, y, rowAndColumnCandidates[0], nameof(Strategy.RowColumnSolver)); return(true); } } // determine if various combinations of rows and columns present a solution // col1[row] is candidate for all following cases // the following set of cases involve a row or a column with: // a candidate, one cell == 0 and the other non zero // the cases are looking for a set of rows and columns // that project the same value except for the row or column with // the non-zero value (which doesn't require a projected value) // check rows if (boxRow1Candidates.Length > 0) { var solverKind = nameof(Strategy.ColumnCandidateColumnBlockedRowSolver); // row1[col3] has a value, so col3 don't need to participate in providing a candidate if (col2Candidates.Length > 0 && boxRow1[col2Index] == 0 && boxRow1[col3Index] != 0 && TrySolveForIntersectionCandidates(boxRow1Candidates, col2Candidates, solverKind, out solution)) { return(true); } // row1[col2] has a value, so col2 don't need to participate in providing a candidate if (col3Candidates.Length > 0 && boxRow1[col2Index] != 0 && boxRow1[col3Index] == 0 && TrySolveForIntersectionCandidates(boxRow1Candidates, col3Candidates, solverKind, out solution)) { return(true); } } // check columns if (boxCol1Candidates.Length > 0) { var solverKind = nameof(Strategy.RowCandidateRowBlockedColumnSolver); // col1[row3] has a value, so row3 don't need to participate in providing a candidate if (row2Candidates.Length > 0 && boxCol1[row2Index] == 0 && boxCol1[row3Index] != 0 && TrySolveForIntersectionCandidates(boxCol1Candidates, row2Candidates, solverKind, out solution)) { return(true); } // col1[row2] has a value, so row2 don't need to participate in providing a candidate // test: SolutionTwoSlotsEmptyInColumn if (row3Candidates.Length > 0 && boxCol1[row2Index] != 0 && boxCol1[row3Index] == 0 && TrySolveForIntersectionCandidates(boxCol1Candidates, row3Candidates, solverKind, out solution)) { return(true); } } // the following set of cases involve a row or column with: // all cells filled, such that a row or column doesn't need to participate in providing a candidate // col3 is blocked, so col3 doesn't need to participate in providing a candidate bool col2Blocked = boxCol2.GetUnsolvedCount() == 0; bool col3Blocked = boxCol3.GetUnsolvedCount() == 0; bool row2Blocked = boxRow2.GetUnsolvedCount() == 0; bool row3Blocked = boxRow3.GetUnsolvedCount() == 0; if (boxCol1.GetUnsolvedCount() == 1) { var solverKind = nameof(Strategy.ColumnCandidateColumnBlocked); // Column 1 one slot open, column 2 blocked, column 3 offers a candidate if (col2Blocked && col3Candidates.Length == 1) { solution = GetSolution(index, i, y, col3Candidates[0], solverKind); return(true); } // Column 1 one slot open, column 2 offers a candidate, column 3 blocked if (col3Blocked && col2Candidates.Length == 1) { solution = GetSolution(index, i, y, col2Candidates[0], solverKind); return(true); } } if (boxRow1.GetUnsolvedCount() == 1) { var solverKind = nameof(Strategy.RowCandidateRowBlocked); // Row 1 one slot open, row 2 blocked, row 3 offers a candidate if (row2Blocked && row3Candidates.Length == 1) { solution = GetSolution(index, i, y, row3Candidates[0], solverKind); return(true); } // Column 1 one slot open, column 2 offers a candidate, column 3 blocked if (row3Blocked && row2Candidates.Length == 1) { solution = GetSolution(index, i, y, row2Candidates[0], solverKind); return(true); } } // following set of cases test hidden singles in columns or rows // pattern // box 1 col1 is full // box 2 has one value not in col1 // only one slot available in col1 in current box // means that current cell must have that value { var solverKind = nameof(Strategy.ColumnLastPossibleSlot); var candidates1 = avnb1.AsLine().DisjointSet(currentCol); foreach (var candidate in candidates1) { if (!boxLine.ContainsValue(candidate) && !currentRow.ContainsValue(candidate) && CheckForValueInCellOrRow(candidate, box, col1Index, row2Index, row3Index) && CheckForValueInCellOrRow(candidate, avnb2, col1Index, 0, 1, 2) ) { solution = GetSolution(index, i, y, candidate, solverKind); return(true); } } var candidates2 = avnb2.AsLine().DisjointSet(currentCol); foreach (var candidate in candidates2) { if (!boxLine.ContainsValue(candidate) && !currentRow.ContainsValue(candidate) && CheckForValueInCellOrRow(candidate, box, col1Index, row2Index, row3Index) && CheckForValueInCellOrRow(candidate, avnb1, col1Index, 0, 1, 2) ) { solution = GetSolution(index, i, y, candidate, solverKind); return(true); } } } { var solverKind = nameof(Strategy.RowLastPossibleSlot); var candidates1 = ahnb1.AsLine().DisjointSet(currentRow); foreach (var candidate in candidates1) { if (!boxLine.ContainsValue(candidate) && !currentCol.Segment.Contains(candidate) && CheckForValueInCellOrColumn(candidate, box, row1Index, col2Index, col3Index) && CheckForValueInCellOrColumn(candidate, ahnb2, row1Index, 0, 1, 2) ) { solution = GetSolution(index, i, y, candidate, solverKind); return(true); } } var candidates2 = ahnb2.AsLine().DisjointSet(currentRow); foreach (var candidate in candidates2) { if (!boxLine.ContainsValue(candidate) && !currentCol.Segment.Contains(candidate) && CheckForValueInCellOrColumn(candidate, box, row1Index, col2Index, col3Index) && CheckForValueInCellOrColumn(candidate, ahnb1, row1Index, 0, 1, 2) ) { solution = GetSolution(index, i, y, candidate, solverKind); return(true); } } } // Strategy where a column or row has two empty cells and one is constrained { var solverKind = nameof(Strategy.ColumnLastTwoPossibleSlots); if (currentCol.GetUnsolvedCount() == 2) { var missingValues = currentCol.GetMissingValues(); for (int k = 0; k < 2; k++) { var value = missingValues[k]; var otherValue = missingValues[(k + 1) % 2]; if (currentRow.ContainsValue(value) && !boxLine.ContainsValue(otherValue)) { solution = GetSolution(index, i, y, otherValue, solverKind); return(true); } } } } { var solverKind = nameof(Strategy.RowLastTwoPossibleSlots); if (currentRow.GetUnsolvedCount() == 2) { var missingValues = currentRow.GetMissingValues(); for (int k = 0; k < 2; k++) { var value = missingValues[k]; var otherValue = missingValues[(k + 1) % 2]; if (currentCol.ContainsValue(value) && !boxLine.ContainsValue(otherValue)) { solution = GetSolution(index, i, y, otherValue, solverKind); return(true); } } } } { var solverKind = nameof(Strategy.LastInRowOrColumn); (var valuesFound, var values) = FindMissingValues(currentCol, currentRow); for (var j = 1; j < 10; j++) { if (values[j]) { continue; } var columnIndex = box.GetColumnOffset() + y; var rowIndex = box.GetRowOffset() + i; var foundSolution = 0; // try with columns for the row for (int l = 0; l < 9; l++) { var cell = currentRow[l]; if (cell != 0 || l == columnIndex) { continue; } if (_puzzle.GetColumn(l).ContainsValue(j)) { foundSolution = j; } else { foundSolution = 0; break; } } if (foundSolution != 0) { solution = GetSolution(index, i, y, foundSolution, solverKind); return(true); } // try with rows for the column for (int l = 0; l < 9; l++) { var cell = currentCol[l]; if (cell != 0 || l == columnIndex) { continue; } if (_puzzle.GetRow(l).ContainsValue(j)) { foundSolution = j; } else { foundSolution = 0; break; } } if (foundSolution != 0) { solution = GetSolution(index, i, y, foundSolution, solverKind); return(true); } } } bool CheckRowForValue(int searchValue, Box box, int row) { // get complete row that includes current row of box var rowIndex = box.GetRowOffset() + row; //TODO: consider an overload that only returns non-zero values var targetRow = _puzzle.GetRow(rowIndex); return(targetRow.Segment.Contains(searchValue)); } bool CheckForValueInCellOrColumn(int value, Box box, int row, params int[] cols) { foreach (int col in cols) { var targetColumn = box.GetColumn(col); if (targetColumn[row] != 0 || ColumnContainsValue(value, box, col)) { } else { return(false); } } return(true); } bool CheckForValueInCellOrRow(int value, Box box, int col, params int[] rows) { foreach (int row in rows) { var targetRow = box.GetRow(row); if (targetRow[col] != 0 || CheckRowForValue(value, box, row)) { } else { return(false); } } return(true); } // assumes lines are of the same length (int valuesFound, bool[] values) FindMissingValues(Line line1, Line line2) { int valuesFound = 0; bool[] values = new bool[10]; for (int i = 0; i < line1.Length; i++) { Update(line1[i]); Update(line2[i]); } return(valuesFound, values); void Update(int value) { if (value != 0 && !values[value]) { values[value] = true; valuesFound++; } } } bool ColumnContainsValue(int searchValue, Box box, int col) { // get complete column that includes current column of box var colIndex = box.GetColumnOffset() + col; var targetCol = _puzzle.GetColumn(colIndex); return(targetCol.Segment.Contains(searchValue)); } bool TrySolveForIntersectionCandidates(ReadOnlySpan <int> candidate1, ReadOnlySpan <int> candidate2, string solverKind, out Solution solution) { var candidates = candidate1.Intersect(candidate2); if (candidates.Length == 1) { solution = GetSolution(index, i, y, candidates[0], solverKind); return(true); } solution = null; return(false); } } Solution GetSolution(int box, int row, int column, int value, string solverKind) { (var r, var c) = Puzzle.GetLocationForBoxCell(index, (row * 3) + column); return(new Solution( r, c, value, nameof(HiddenSinglesSolver), solverKind)); } } solution = null; return(false); }