denis_berthier wrote:As you're speaking of the hardest puzzles, what you call T&E must be what I call T&E(2), i.e. there must be two levels of T&E.
No, for these puzzles I am not counting the T&E depth. Also I don't try to follow a kind of optimal solution path with the only exception that I am chosing for T&E a bivalue cell, and if no bivalue is found, I am searching for a n-local, stopping at first bi-local, or chosing cell/value from the first occurence of min(n).
denis_berthier wrote:For the hard puzzles in T&E(1) or g-T&E(1), the heuristics consisting of looking first for eliminations in bivalue/bilocal cells is generally counter-productive. This can be seen from the resolution paths: such an elimination would be followed by a single; but singles rarely appear in the long sequences of whips or g-whips leading to the solution (as in my above solution).
For the very rare (in percentage) puzzles in T&E(2), it may be the case that things are different, but the question is: after selecting the first candidate for T&E at level 1 in a bivalue/bilocal cell, many candidates may be (locally) eliminated by this first selection, and at the time when you select a second candidate for the second level of T&E, it may appear to be bivalue/bilocal only because of these level 1 local eliminations.
Agree, but what are the computationally cheaper alternatives? Chosing a random cell/value within the candidates is proven to work for several times slower, and chosing the first/last candidate is even worse.
denis_berthier wrote:Could you be more precise about your T&E procedure?
Note: I preffer not to discuss the differences between T&E and guessing. If you consider this is guessing, I fully agree.
General algorithm is
1) Init. Exit if all cells are givens.
2) Deal with singles. Exit if the necessary number of solutions are found. On solution and contradiction goto step 4.
3) Choose a cell/value pair for T&E. Clone the puzzle context and eliminate the chosen cell/value from the cloned context. Add the chosen cell/value as part of the solution in the curreent context and goto step 2 using the current context.
4) Backtrack by restoring the latest context with the tried candidate removed in step 3. Exit if there is no context to restore.
More precise explanation of the T&E procedure is
3.a) Scan the context for bi-value cells. Fast. If none found go to step 3.c.
3.b) Chose the first value which pencilmarks have non-empty intersection with the bi-values. Find the first cell from the intersection. Goto step 3.d.
3.c) Iterate trough all values and all unsolved houses for the respective value. Count the number of candidates n for the respective value/house. If n < previous n then find the first unsolved cell from that house and store the best so far n/value/cell. If n = 2 then goto step 3.d else continue with next house then value.
3.d) There is a chosen "best" cell/value for T&E set either from step 3.b or 3.c. Copy the 160-bytes long context in a stack. Eliminate the chosen cell/value in the stack. Mark the chosen cell/value as "solved" in the current context.
3.e) Go to step 1 (solve singles).
Yet more precise T&E is the code itself, available at
https://github.com/dobrichev/fsss2/, at the time of writing this post it is close to the end of the file fsss2.cpp.
There is a topic for discussion of the solver in the Programmers forum (
http://www.setbb.com/sudoku/viewtopic.php?t=2482&mforum=sudoku).
Below is a piece of the actual code
- Code: Select all
void fsss2::doEliminations() {
nakedAgain:
doNakedSingles();
if(mode) goto backtrack;
//hidden singles
{
............
} //end of hidden singles
//Prepare a guess
{
int optDigit;
int optCell;
//find first bi-value cell and return first of the two values
findBiValueCell(optDigit, optCell);
if(optDigit != -1) {
;
}
else {
//find house with less candidates from a particular digit, exit on first bi-position house/digit
findBiPositionDigit(optDigit, optCell);
}
{
bm128* gg = &contexts[guessDepth++][0];
gg[0] = grid[0];
gg[1] = grid[1];
gg[2] = grid[2];
gg[3] = grid[3];
gg[4] = grid[4];
gg[5] = grid[5];
gg[6] = grid[6];
gg[7] = grid[7];
gg[8] = grid[8];
gg[9] = solved;
//later continue with this possibility eliminated
gg[optDigit].clearBit(optCell);
if(sol)
sol[optCell] = optDigit + 1; //store the digit if solution buffer is given
solved.setBit(optCell); //mark cell as "solved"
grid[optDigit].clearBits(visibleCells[optCell]); //mark visible cells as forbidden for the same digit, mark the 3 houses as solved
goto nakedAgain;
}
}
backtrack:
if(mode & MODE_STOP_GUESSING) { //no need to restore context
return;
}
contradiction:
if(guessDepth-- == 0) { //nothing to restore
return;
}
{
//We are done with the guess.
//The caller is notified for each of the the possible solutions found so far
//Now restore the context. The just guessed candidate has been removed from the context earlier.
bm128* gg = &contexts[guessDepth][0];
grid[0] = gg[0];
grid[1] = gg[1];
grid[2] = gg[2];
grid[3] = gg[3];
grid[4] = gg[4];
grid[5] = gg[5];
grid[6] = gg[6];
grid[7] = gg[7];
grid[8] = gg[8];
solved = gg[9];
mode = 0;
}
goto nakedAgain;
}