@wsabol/sudoku-solver 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/sudokuSolver.d.ts +7 -3
- package/dist/sudokuSolver.d.ts.map +1 -1
- package/dist/sudokuSolver.js +102 -15
- package/package.json +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClI,OAAO,EAA8C,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzH,UAAU,WAAW;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CAChB;AAED,UAAU,cAAc;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,UAAU,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEzD,UAAU,UAAU;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,WAAW,CAOtD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAClI,OAAO,EAA8C,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzH,UAAU,WAAW;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CAChB;AAED,UAAU,cAAc;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,UAAU,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,CAAC;AAEzD,UAAU,UAAU;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,iBAAS,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,WAAW,CAOtD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,CAkCxD;AAED,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,gBAAgB,CAY9D;AAED,iBAAS,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,cAAc,CAoCjE;AAED,QAAA,MAAM,MAAM;;;;;CAKX,CAAC;AAEF,OAAO,EACH,YAAY,EACf,CAAA;AAGD,YAAY,EACR,SAAS,EACT,KAAK,EACL,IAAI,EACJ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACjB,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,8 +28,11 @@ function nextMove(boardInput) {
|
|
|
28
28
|
sudoku.setSquareValue(move.row, move.col, move.value);
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const digits = [...new Set(move.eliminations.map((e) => e.value))].sort((a, b) => a - b);
|
|
32
|
+
const digitPart = digits.length === 1
|
|
33
|
+
? String(digits[0])
|
|
34
|
+
: `{${digits.join(",")}}`;
|
|
35
|
+
message = `Eliminate ${digitPart} from ${move.eliminations.length} cell(s) (${move.algorithm})`;
|
|
33
36
|
sudoku.applyElimination(move);
|
|
34
37
|
}
|
|
35
38
|
return {
|
package/dist/sudokuSolver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValidationResult } from "./validate.js";
|
|
2
2
|
export type Board = number[][];
|
|
3
|
-
export type Algorithm = "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair/Triple";
|
|
3
|
+
export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair/Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad";
|
|
4
4
|
export interface PlacementMove {
|
|
5
5
|
type: "placement";
|
|
6
6
|
row: number;
|
|
@@ -20,13 +20,14 @@ export interface EliminationMove {
|
|
|
20
20
|
export type Move = PlacementMove | EliminationMove;
|
|
21
21
|
export type DifficultyLevel = "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible";
|
|
22
22
|
export default class SudokuSolver {
|
|
23
|
-
static readonly
|
|
23
|
+
private static readonly SEARCH_PHASES;
|
|
24
24
|
private board;
|
|
25
25
|
private possiblesGrid;
|
|
26
26
|
constructor(input: string | Board);
|
|
27
27
|
setBoard(board: Board): void;
|
|
28
28
|
toArray(): Board;
|
|
29
29
|
getPossibles(row: number, col: number): number[];
|
|
30
|
+
setPossibles(row: number, col: number, possibles: number[]): void;
|
|
30
31
|
setSquareValue(row: number, col: number, value: number): void;
|
|
31
32
|
applyElimination(move: EliminationMove): void;
|
|
32
33
|
isComplete(): boolean;
|
|
@@ -45,12 +46,15 @@ export default class SudokuSolver {
|
|
|
45
46
|
private calcSquarePossibles;
|
|
46
47
|
private calcPossibles;
|
|
47
48
|
private findBestMove;
|
|
48
|
-
private
|
|
49
|
+
private findMoveForPhase;
|
|
49
50
|
private findNakedSingle;
|
|
50
51
|
private findHiddenSingleInRow;
|
|
51
52
|
private findHiddenSingleInCol;
|
|
52
53
|
private findHiddenSingleInBox;
|
|
53
54
|
private findPointingPairTriple;
|
|
55
|
+
private eachHouseInOrder;
|
|
56
|
+
private findNakedSubsetElimination;
|
|
57
|
+
private tryNakedSubsetInHouse;
|
|
54
58
|
private findHiddenSingle;
|
|
55
59
|
}
|
|
56
60
|
//# sourceMappingURL=sudokuSolver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sudokuSolver.d.ts","sourceRoot":"","sources":["../src/sudokuSolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAoB,MAAM,eAAe,CAAC;AAErF,MAAM,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;AAE/B,MAAM,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"sudokuSolver.d.ts","sourceRoot":"","sources":["../src/sudokuSolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAoB,MAAM,eAAe,CAAC;AAErF,MAAM,MAAM,KAAK,GAAG,MAAM,EAAE,EAAE,CAAC;AAE/B,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,cAAc,GACd,YAAY,CAAC;AAKnB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,SAAS,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,IAAI,GAAG,aAAa,GAAG,eAAe,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAIvF,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAKnC;IAEF,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAe;gBAExB,KAAK,EAAE,MAAM,GAAG,KAAK;IAOjC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAS5B,OAAO,IAAI,KAAK;IAIhB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAIhD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAIjE,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAmB7D,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAM7C,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAkBhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,eAAe;IAuCvB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,gBAAgB;CAe3B"}
|
package/dist/sudokuSolver.js
CHANGED
|
@@ -2,7 +2,12 @@ import { cloneBoard, assertBoardShape, parseBoardString, duplicateValues } from
|
|
|
2
2
|
import { pushUniqueReason } from "./validate.js";
|
|
3
3
|
const COMPLETE = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
4
4
|
export default class SudokuSolver {
|
|
5
|
-
static
|
|
5
|
+
static SEARCH_PHASES = [
|
|
6
|
+
"NakedSingle",
|
|
7
|
+
"HiddenSingle",
|
|
8
|
+
"Pointing",
|
|
9
|
+
"NakedSubset",
|
|
10
|
+
];
|
|
6
11
|
board;
|
|
7
12
|
possiblesGrid;
|
|
8
13
|
constructor(input) {
|
|
@@ -23,6 +28,9 @@ export default class SudokuSolver {
|
|
|
23
28
|
getPossibles(row, col) {
|
|
24
29
|
return [...this.possiblesGrid[row][col]];
|
|
25
30
|
}
|
|
31
|
+
setPossibles(row, col, possibles) {
|
|
32
|
+
this.possiblesGrid[row][col] = possibles.filter((v) => v > 0 && v <= 9).sort((a, b) => a - b);
|
|
33
|
+
}
|
|
26
34
|
setSquareValue(row, col, value) {
|
|
27
35
|
this.board[row][col] = value;
|
|
28
36
|
this.possiblesGrid[row][col] = [];
|
|
@@ -228,27 +236,23 @@ export default class SudokuSolver {
|
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
findBestMove() {
|
|
231
|
-
for (const
|
|
232
|
-
const move = this.
|
|
239
|
+
for (const phase of SudokuSolver.SEARCH_PHASES) {
|
|
240
|
+
const move = this.findMoveForPhase(phase);
|
|
233
241
|
if (move)
|
|
234
242
|
return move;
|
|
235
243
|
}
|
|
236
244
|
return null;
|
|
237
245
|
}
|
|
238
|
-
|
|
239
|
-
switch (
|
|
240
|
-
case "
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (algorithm === "Full House") {
|
|
244
|
-
return move?.algorithm === "Full House" ? move : null;
|
|
245
|
-
}
|
|
246
|
-
return move;
|
|
247
|
-
}
|
|
248
|
-
case "Hidden Single":
|
|
246
|
+
findMoveForPhase(phase) {
|
|
247
|
+
switch (phase) {
|
|
248
|
+
case "NakedSingle":
|
|
249
|
+
return this.findNakedSingle();
|
|
250
|
+
case "HiddenSingle":
|
|
249
251
|
return this.findHiddenSingle();
|
|
250
|
-
case "Pointing
|
|
252
|
+
case "Pointing":
|
|
251
253
|
return this.findPointingPairTriple();
|
|
254
|
+
case "NakedSubset":
|
|
255
|
+
return this.findNakedSubsetElimination();
|
|
252
256
|
}
|
|
253
257
|
}
|
|
254
258
|
findNakedSingle() {
|
|
@@ -272,6 +276,13 @@ export default class SudokuSolver {
|
|
|
272
276
|
if (boxValues.filter((v) => v === 0).length === 1) {
|
|
273
277
|
algo = "Full House";
|
|
274
278
|
}
|
|
279
|
+
if (algo === "Naked Single") {
|
|
280
|
+
// check if last digit
|
|
281
|
+
const placementsOfDigit = this.board.flat().filter((v) => v === placeValue).length;
|
|
282
|
+
if (placementsOfDigit === 8) {
|
|
283
|
+
algo = "Last Digit";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
275
286
|
return { type: "placement", row, col, value: placeValue, algorithm: algo };
|
|
276
287
|
}
|
|
277
288
|
}
|
|
@@ -368,6 +379,82 @@ export default class SudokuSolver {
|
|
|
368
379
|
}
|
|
369
380
|
return null;
|
|
370
381
|
}
|
|
382
|
+
eachHouseInOrder() {
|
|
383
|
+
const houses = [];
|
|
384
|
+
for (let r = 0; r < 9; r++) {
|
|
385
|
+
houses.push(Array.from({ length: 9 }, (_, c) => ({ row: r, col: c })));
|
|
386
|
+
}
|
|
387
|
+
for (let c = 0; c < 9; c++) {
|
|
388
|
+
houses.push(Array.from({ length: 9 }, (_, row) => ({ row, col: c })));
|
|
389
|
+
}
|
|
390
|
+
for (let b = 0; b < 9; b++) {
|
|
391
|
+
const cells = [];
|
|
392
|
+
for (let idx = 0; idx < 9; idx++) {
|
|
393
|
+
cells.push(this.boxToPuzzle(b, idx));
|
|
394
|
+
}
|
|
395
|
+
houses.push(cells);
|
|
396
|
+
}
|
|
397
|
+
return houses;
|
|
398
|
+
}
|
|
399
|
+
findNakedSubsetElimination() {
|
|
400
|
+
for (const houseCells of this.eachHouseInOrder()) {
|
|
401
|
+
for (let k = 2; k <= 4; k++) {
|
|
402
|
+
const move = this.tryNakedSubsetInHouse(houseCells, k);
|
|
403
|
+
if (move)
|
|
404
|
+
return move;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
tryNakedSubsetInHouse(houseCells, k) {
|
|
410
|
+
const empties = houseCells.filter(({ row, col }) => this.board[row][col] === 0 && this.possiblesGrid[row][col].length > 0);
|
|
411
|
+
if (empties.length < k) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const algorithm = k === 2 ? "Naked Pair" : k === 3 ? "Naked Triple" : "Naked Quad";
|
|
415
|
+
const n = empties.length;
|
|
416
|
+
const indices = [];
|
|
417
|
+
const dfs = (start) => {
|
|
418
|
+
if (indices.length === k) {
|
|
419
|
+
const subset = indices.map((i) => empties[i]);
|
|
420
|
+
const union = new Set();
|
|
421
|
+
for (const { row, col } of subset) {
|
|
422
|
+
for (const v of this.possiblesGrid[row][col]) {
|
|
423
|
+
union.add(v);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (union.size !== k) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const subsetKeys = new Set(subset.map(({ row, col }) => row * 9 + col));
|
|
430
|
+
const eliminations = [];
|
|
431
|
+
for (const { row, col } of houseCells) {
|
|
432
|
+
if (this.board[row][col] !== 0 || subsetKeys.has(row * 9 + col)) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const poss = this.possiblesGrid[row][col];
|
|
436
|
+
for (const d of union) {
|
|
437
|
+
if (poss.includes(d)) {
|
|
438
|
+
eliminations.push({ row, col, value: d });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (eliminations.length === 0) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
return { type: "elimination", eliminations, algorithm };
|
|
446
|
+
}
|
|
447
|
+
for (let i = start; i < n; i++) {
|
|
448
|
+
indices.push(i);
|
|
449
|
+
const res = dfs(i + 1);
|
|
450
|
+
indices.pop();
|
|
451
|
+
if (res)
|
|
452
|
+
return res;
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
};
|
|
456
|
+
return dfs(0);
|
|
457
|
+
}
|
|
371
458
|
findHiddenSingle() {
|
|
372
459
|
for (let row = 0; row < 9; row++) {
|
|
373
460
|
const move = this.findHiddenSingleInRow(row);
|
package/package.json
CHANGED