@wsabol/sudoku-solver 0.1.6 → 0.1.7
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/sudokuSolver.d.ts +4 -1
- package/dist/sudokuSolver.d.ts.map +1 -1
- package/dist/sudokuSolver.js +93 -2
- package/package.json +1 -1
package/dist/sudokuSolver.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ValidationResult } from "./validate.js";
|
|
2
2
|
import type { Board } from "./boardGeo.js";
|
|
3
3
|
import type { Move, EliminationMove } from "./move.js";
|
|
4
|
-
export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair" | "Pointing Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad";
|
|
4
|
+
export type Algorithm = "Last Digit" | "Full House" | "Naked Single" | "Hidden Single" | "Pointing Pair" | "Pointing Triple" | "Naked Pair" | "Naked Triple" | "Naked Quad" | "Hidden Pair" | "Hidden Triple" | "Hidden Quad";
|
|
5
5
|
export type DifficultyLevel = "Easy" | "Medium" | "Hard" | "Diabolical" | "Impossible";
|
|
6
6
|
export type ValidationReasonType = "duplicate_in_row" | "duplicate_in_column" | "duplicate_in_box" | "invalid_value" | "invalid_board_length" | "invalid_board_characters" | "empty_cell_no_candidates" | "too_many_empty_cells";
|
|
7
7
|
export default class SudokuSolver {
|
|
@@ -48,6 +48,9 @@ export default class SudokuSolver {
|
|
|
48
48
|
/** `houseCells` is one full row, column, or box (9 cells). */
|
|
49
49
|
private getHouseContext;
|
|
50
50
|
private tryNakedSubsetInHouse;
|
|
51
|
+
private findHiddenSubsetElimination;
|
|
52
|
+
private findNakedHiddenQuadsElimination;
|
|
53
|
+
private tryHiddenSubsetInHouse;
|
|
51
54
|
private findHiddenSingle;
|
|
52
55
|
}
|
|
53
56
|
//# 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;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAiB,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtE,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,cAAc,GACd,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sudokuSolver.d.ts","sourceRoot":"","sources":["../src/sudokuSolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAoB,MAAM,eAAe,CAAC;AACrF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAiB,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtE,MAAM,MAAM,SAAS,GACf,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,eAAe,GACf,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,cAAc,GACd,YAAY,GACZ,aAAa,GACb,eAAe,GACf,aAAa,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,GAAG,YAAY,CAAC;AAEvF,MAAM,MAAM,oBAAoB,GAC1B,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,eAAe,GACf,sBAAsB,GACtB,0BAA0B,GAC1B,0BAA0B,GAC1B,sBAAsB,CAAC;AA+C7B,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAOnC;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,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAQ3B,UAAU,IAAI,OAAO;IAIrB,eAAe,IAAI,MAAM;IAIzB,WAAW,CAAC,KAAK,GAAE,MAAU,GAAG,MAAM;IAOtC,QAAQ,IAAI,gBAAgB;IAiG5B,OAAO,IAAI,OAAO;IAKlB,UAAU,IAAI,eAAe;IAiB7B,WAAW,IAAI,IAAI,GAAG,IAAI;IAO1B,KAAK,IAAI,OAAO;IAchB,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,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,eAAe;IA+CvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAsB7B,OAAO,CAAC,qBAAqB;IAuB7B,OAAO,CAAC,sBAAsB;IAqD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,8DAA8D;IAC9D,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,qBAAqB;IAwD7B,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,+BAA+B;IAWvC,OAAO,CAAC,sBAAsB;IAgE9B,OAAO,CAAC,gBAAgB;CAe3B"}
|
package/dist/sudokuSolver.js
CHANGED
|
@@ -40,6 +40,8 @@ export default class SudokuSolver {
|
|
|
40
40
|
"HiddenSingle",
|
|
41
41
|
"Pointing",
|
|
42
42
|
"NakedSubset",
|
|
43
|
+
"HiddenSubset",
|
|
44
|
+
"NakedHiddenQuads",
|
|
43
45
|
];
|
|
44
46
|
board;
|
|
45
47
|
possiblesGrid;
|
|
@@ -290,7 +292,7 @@ export default class SudokuSolver {
|
|
|
290
292
|
}
|
|
291
293
|
finalizeElimination(eliminations, algorithm, reasoning) {
|
|
292
294
|
const digits = [...new Set(eliminations.map((e) => e.value))].sort((a, b) => a - b);
|
|
293
|
-
const digitPart = digits.length === 1 ? String(digits[0]) :
|
|
295
|
+
const digitPart = digits.length === 1 ? String(digits[0]) : `${digits.join("/")}`;
|
|
294
296
|
const uniqueCells = uniqueCellCoordinates(eliminations);
|
|
295
297
|
const uniqueCellCount = uniqueCells.length;
|
|
296
298
|
const cellWord = uniqueCellCount === 1 ? "cell" : "cells";
|
|
@@ -322,6 +324,12 @@ export default class SudokuSolver {
|
|
|
322
324
|
return this.findPointingPairTriple();
|
|
323
325
|
case "NakedSubset":
|
|
324
326
|
return this.findNakedSubsetElimination();
|
|
327
|
+
case "HiddenSubset":
|
|
328
|
+
return this.findHiddenSubsetElimination();
|
|
329
|
+
case "NakedHiddenQuads":
|
|
330
|
+
return this.findNakedHiddenQuadsElimination();
|
|
331
|
+
default:
|
|
332
|
+
return null;
|
|
325
333
|
}
|
|
326
334
|
}
|
|
327
335
|
findNakedSingle() {
|
|
@@ -509,7 +517,7 @@ export default class SudokuSolver {
|
|
|
509
517
|
}
|
|
510
518
|
findNakedSubsetElimination() {
|
|
511
519
|
for (const houseCells of this.eachHouseInOrder()) {
|
|
512
|
-
for (let k = 2; k <=
|
|
520
|
+
for (let k = 2; k <= 3; k++) {
|
|
513
521
|
const move = this.tryNakedSubsetInHouse(houseCells, k);
|
|
514
522
|
if (move)
|
|
515
523
|
return move;
|
|
@@ -578,6 +586,89 @@ export default class SudokuSolver {
|
|
|
578
586
|
};
|
|
579
587
|
return dfs(0);
|
|
580
588
|
}
|
|
589
|
+
findHiddenSubsetElimination() {
|
|
590
|
+
for (const houseCells of this.eachHouseInOrder()) {
|
|
591
|
+
for (let k = 2; k <= 3; k++) {
|
|
592
|
+
const move = this.tryHiddenSubsetInHouse(houseCells, k);
|
|
593
|
+
if (move)
|
|
594
|
+
return move;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
findNakedHiddenQuadsElimination() {
|
|
600
|
+
for (const houseCells of this.eachHouseInOrder()) {
|
|
601
|
+
const moveNaked = this.tryNakedSubsetInHouse(houseCells, 4);
|
|
602
|
+
if (moveNaked)
|
|
603
|
+
return moveNaked;
|
|
604
|
+
const moveHidden = this.tryHiddenSubsetInHouse(houseCells, 4);
|
|
605
|
+
if (moveHidden)
|
|
606
|
+
return moveHidden;
|
|
607
|
+
}
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
tryHiddenSubsetInHouse(houseCells, k) {
|
|
611
|
+
const algorithm = k === 2 ? "Hidden Pair" : k === 3 ? "Hidden Triple" : "Hidden Quad";
|
|
612
|
+
const { wherePhrase, sameKindWord } = this.getHouseContext(houseCells);
|
|
613
|
+
const digitIndices = [];
|
|
614
|
+
const dfsDigits = (start) => {
|
|
615
|
+
if (digitIndices.length === k) {
|
|
616
|
+
const digits = digitIndices.map((i) => COMPLETE[i]);
|
|
617
|
+
const digitSet = new Set(digits);
|
|
618
|
+
for (const d of digits) {
|
|
619
|
+
let seen = false;
|
|
620
|
+
for (const { row, col } of houseCells) {
|
|
621
|
+
if (this.board[row][col] === 0 && this.possiblesGrid[row][col].includes(d)) {
|
|
622
|
+
seen = true;
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (!seen) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const reserved = [];
|
|
631
|
+
for (const { row, col } of houseCells) {
|
|
632
|
+
if (this.board[row][col] !== 0) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
for (const d of digits) {
|
|
636
|
+
if (this.possiblesGrid[row][col].includes(d)) {
|
|
637
|
+
reserved.push({ row, col });
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (reserved.length !== k) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
const eliminations = [];
|
|
646
|
+
for (const { row, col } of reserved) {
|
|
647
|
+
for (const v of this.possiblesGrid[row][col]) {
|
|
648
|
+
if (!digitSet.has(v)) {
|
|
649
|
+
eliminations.push({ row, col, value: v });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (eliminations.length === 0) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
const digitStr = [...digits].sort((a, b) => a - b).join("/");
|
|
657
|
+
const cellPhrase = reserved.map(({ row, col }) => `r${row + 1}c${col + 1}`).join(", ");
|
|
658
|
+
const reasoning = `${algorithm} ${digitStr} in ${wherePhrase}: in that ${sameKindWord}, those digits appear only in ${cellPhrase}, so candidates other than ${digitStr} can be removed from those cells.`;
|
|
659
|
+
return this.finalizeElimination(eliminations, algorithm, reasoning);
|
|
660
|
+
}
|
|
661
|
+
for (let i = start; i < 9; i++) {
|
|
662
|
+
digitIndices.push(i);
|
|
663
|
+
const res = dfsDigits(i + 1);
|
|
664
|
+
digitIndices.pop();
|
|
665
|
+
if (res)
|
|
666
|
+
return res;
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
669
|
+
};
|
|
670
|
+
return dfsDigits(0);
|
|
671
|
+
}
|
|
581
672
|
findHiddenSingle() {
|
|
582
673
|
for (let row = 0; row < 9; row++) {
|
|
583
674
|
const move = this.findHiddenSingleInRow(row);
|
package/package.json
CHANGED