optimize generate()

Before we masked all except the removed cell rows, then unmasked all
rows on every removal attempt.

Now, we mask all except the removed cell rows once each attempt in an
order such that we can simply unmask the last (values - 1) rows to try
the next cell. An unmask/remask cycle is only required when a removal
attempt fails.
This commit is contained in:
Matt Low 2021-03-03 23:15:15 -07:00
parent 938954d621
commit 8526dcd083

View File

@ -144,17 +144,14 @@ export class SudokuMath {
generateComplete(): [number[], number] { generateComplete(): [number[], number] {
const result = this._baseBoard(); const result = this._baseBoard();
const [header] = this.getDLXHeader(result, true); const [header] = this.getDLXHeader(result, true);
const dlx = new DLX(header, (solution: DNode[]) => {
const callback = (solution: DNode[]) => {
solution.forEach((node) => { solution.forEach((node) => {
const meta: NodeMeta = node.meta; const meta: NodeMeta = node.meta;
result[meta.index] = meta.value; result[meta.index] = meta.value;
}); });
// return the first solution // stop after the first solution
return true; return true;
}; });
const dlx = new DLX(header, callback);
dlx.search(); dlx.search();
return [result, dlx.updates]; return [result, dlx.updates];
@ -176,10 +173,6 @@ export class SudokuMath {
candidates[meta.index][meta.value - 1] = node; candidates[meta.index][meta.value - 1] = node;
}); });
// board positions which have been removed
const removed = new Set<number>();
const masked: DNode[] = [];
const hasOneSolution = () => { const hasOneSolution = () => {
solutions = 0; solutions = 0;
dlx.search(); dlx.search();
@ -187,51 +180,81 @@ export class SudokuMath {
return solutions === 1; return solutions === 1;
}; };
const mask = () => { const start = Date.now();
// mask all DLX rows which are nullified by existing values const elapsed = () => Date.now() - start;
for (let n = 0; n < this.values2; n++) {
if (removed.has(n)) { // masked rows in the order they were masked
continue; const masked: DNode[] = [];
}
const nodes = candidates[n]; const maskAtIdx = (idx: number) => {
const nodes = candidates[idx];
nodes.forEach((node) => { nodes.forEach((node) => {
if (node.meta.value !== completed[n]) { if (node.meta.value !== completed[idx]) {
masked.push(node); masked.push(node);
maskRow(node); maskRow(node);
} }
}); });
};
const removed = new Set<number>();
const removeable = Array.from(this.indexes);
const maskUpto = (n: number) => {
for (let j = 0; j < n; j++) {
// process up to what we've handled
const idx = removeable[j];
if (removed.has(idx)) {
// if we've removed this cell, do not mask it
continue;
}
// otherwise, we had given up it; mask it leaving the original value
// we won't try this cell again
maskAtIdx(idx);
}
for (let j = this.values2 - 1; j >= n; j--) {
// for all those we haven't handled, mask leaving the original values
// for us to to attempt to unmask one cell at a time
maskAtIdx(removeable[j]);
} }
}; };
const unmask = () => { const unmaskAll = () => {
// unmask all DLX rows let node;
while (masked.length > 0) { while ((node = masked.pop())) {
unmaskRow(node);
}
};
const unmaskNextCell = () => {
for (let j = 0; j < this.values - 1; j++) {
unmaskRow(masked.pop()!); unmaskRow(masked.pop()!);
} }
}; };
const start = Date.now();
const elapsed = () => Date.now() - start;
const removeable = Array.from(this.indexes);
const attempt = () => { const attempt = () => {
// attempt remove cells until 'clues' cells remain // attempt remove cells until 'clues' cells remain
shuffle(removeable); shuffle(removeable);
for (let n = 0; n < this.values2; n++) { maskUpto(0);
for (let i = 0; i < this.values2; i++) {
if (elapsed() > totalTime || this.values2 - removed.size == clues) { if (elapsed() > totalTime || this.values2 - removed.size == clues) {
break; break;
} }
let toRemove = removeable[n]; unmaskNextCell();
removed.add(toRemove);
mask();
if (!hasOneSolution()) { if (!hasOneSolution()) {
removed.delete(toRemove); // failed attempt. prepare for next
unmaskAll();
maskUpto(i + 1);
continue;
} }
unmask(); removed.add(removeable[i]);
} }
unmaskAll();
}; };
while ( while (