@wsabol/sudoku-solver 0.1.2 → 0.1.4

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.
@@ -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,CA8BxD;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"}
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 digit = move.eliminations[0].value;
32
- message = `Eliminate ${digit} from ${move.eliminations.length} cell(s) (${move.algorithm})`;
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 {
@@ -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,7 +20,7 @@ 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 ALGORITHMS: Algorithm[];
23
+ private static readonly SEARCH_PHASES;
24
24
  private board;
25
25
  private possiblesGrid;
26
26
  constructor(input: string | Board);
@@ -46,12 +46,15 @@ export default class SudokuSolver {
46
46
  private calcSquarePossibles;
47
47
  private calcPossibles;
48
48
  private findBestMove;
49
- private findNextPlacement;
49
+ private findMoveForPhase;
50
50
  private findNakedSingle;
51
51
  private findHiddenSingleInRow;
52
52
  private findHiddenSingleInCol;
53
53
  private findHiddenSingleInBox;
54
54
  private findPointingPairTriple;
55
+ private eachHouseInOrder;
56
+ private findNakedSubsetElimination;
57
+ private tryNakedSubsetInHouse;
55
58
  private findHiddenSingle;
56
59
  }
57
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,GAAG,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,sBAAsB,CAAC;AAEjG,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,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,CAA2E;IAElH,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,iBAAiB;IAiBzB,OAAO,CAAC,eAAe;IA+BvB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,gBAAgB;CAe3B"}
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;IAuB7B,OAAO,CAAC,qBAAqB;IAuB7B,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,gBAAgB;CAe3B"}
@@ -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 ALGORITHMS = ["Full House", "Naked Single", "Hidden Single", "Pointing Pair/Triple"];
5
+ static SEARCH_PHASES = [
6
+ "NakedSingle",
7
+ "HiddenSingle",
8
+ "Pointing",
9
+ "NakedSubset",
10
+ ];
6
11
  board;
7
12
  possiblesGrid;
8
13
  constructor(input) {
@@ -231,27 +236,23 @@ export default class SudokuSolver {
231
236
  }
232
237
  }
233
238
  findBestMove() {
234
- for (const algorithm of SudokuSolver.ALGORITHMS) {
235
- const move = this.findNextPlacement(algorithm);
239
+ for (const phase of SudokuSolver.SEARCH_PHASES) {
240
+ const move = this.findMoveForPhase(phase);
236
241
  if (move)
237
242
  return move;
238
243
  }
239
244
  return null;
240
245
  }
241
- findNextPlacement(algorithm) {
242
- switch (algorithm) {
243
- case "Full House":
244
- case "Naked Single": {
245
- const move = this.findNakedSingle();
246
- if (algorithm === "Full House") {
247
- return move?.algorithm === "Full House" ? move : null;
248
- }
249
- return move;
250
- }
251
- case "Hidden Single":
246
+ findMoveForPhase(phase) {
247
+ switch (phase) {
248
+ case "NakedSingle":
249
+ return this.findNakedSingle();
250
+ case "HiddenSingle":
252
251
  return this.findHiddenSingle();
253
- case "Pointing Pair/Triple":
252
+ case "Pointing":
254
253
  return this.findPointingPairTriple();
254
+ case "NakedSubset":
255
+ return this.findNakedSubsetElimination();
255
256
  }
256
257
  }
257
258
  findNakedSingle() {
@@ -275,6 +276,13 @@ export default class SudokuSolver {
275
276
  if (boxValues.filter((v) => v === 0).length === 1) {
276
277
  algo = "Full House";
277
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
+ }
278
286
  return { type: "placement", row, col, value: placeValue, algorithm: algo };
279
287
  }
280
288
  }
@@ -291,7 +299,13 @@ export default class SudokuSolver {
291
299
  }
292
300
  }
293
301
  if (candidates.length === 1) {
294
- return { type: "placement", row, col: candidates[0], value, algorithm: "Hidden Single" };
302
+ let move = { type: "placement", row, col: candidates[0], value, algorithm: "Hidden Single" };
303
+ // check if last digit
304
+ const placementsOfDigit = this.board.flat().filter((v) => v === value).length;
305
+ if (placementsOfDigit === 8) {
306
+ move.algorithm = "Last Digit";
307
+ }
308
+ return move;
295
309
  }
296
310
  }
297
311
  return null;
@@ -305,7 +319,13 @@ export default class SudokuSolver {
305
319
  }
306
320
  }
307
321
  if (candidates.length === 1) {
308
- return { type: "placement", row: candidates[0], col, value, algorithm: "Hidden Single" };
322
+ let move = { type: "placement", row: candidates[0], col, value, algorithm: "Hidden Single" };
323
+ // check if last digit
324
+ const placementsOfDigit = this.board.flat().filter((v) => v === value).length;
325
+ if (placementsOfDigit === 8) {
326
+ move.algorithm = "Last Digit";
327
+ }
328
+ return move;
309
329
  }
310
330
  }
311
331
  return null;
@@ -320,7 +340,13 @@ export default class SudokuSolver {
320
340
  }
321
341
  }
322
342
  if (candidates.length === 1) {
323
- return { type: "placement", row: candidates[0].row, col: candidates[0].col, value, algorithm: "Hidden Single" };
343
+ let move = { type: "placement", row: candidates[0].row, col: candidates[0].col, value, algorithm: "Hidden Single" };
344
+ // check if last digit
345
+ const placementsOfDigit = this.board.flat().filter((v) => v === value).length;
346
+ if (placementsOfDigit === 8) {
347
+ move.algorithm = "Last Digit";
348
+ }
349
+ return move;
324
350
  }
325
351
  }
326
352
  return null;
@@ -371,6 +397,82 @@ export default class SudokuSolver {
371
397
  }
372
398
  return null;
373
399
  }
400
+ eachHouseInOrder() {
401
+ const houses = [];
402
+ for (let r = 0; r < 9; r++) {
403
+ houses.push(Array.from({ length: 9 }, (_, c) => ({ row: r, col: c })));
404
+ }
405
+ for (let c = 0; c < 9; c++) {
406
+ houses.push(Array.from({ length: 9 }, (_, row) => ({ row, col: c })));
407
+ }
408
+ for (let b = 0; b < 9; b++) {
409
+ const cells = [];
410
+ for (let idx = 0; idx < 9; idx++) {
411
+ cells.push(this.boxToPuzzle(b, idx));
412
+ }
413
+ houses.push(cells);
414
+ }
415
+ return houses;
416
+ }
417
+ findNakedSubsetElimination() {
418
+ for (const houseCells of this.eachHouseInOrder()) {
419
+ for (let k = 2; k <= 4; k++) {
420
+ const move = this.tryNakedSubsetInHouse(houseCells, k);
421
+ if (move)
422
+ return move;
423
+ }
424
+ }
425
+ return null;
426
+ }
427
+ tryNakedSubsetInHouse(houseCells, k) {
428
+ const empties = houseCells.filter(({ row, col }) => this.board[row][col] === 0 && this.possiblesGrid[row][col].length > 0);
429
+ if (empties.length < k) {
430
+ return null;
431
+ }
432
+ const algorithm = k === 2 ? "Naked Pair" : k === 3 ? "Naked Triple" : "Naked Quad";
433
+ const n = empties.length;
434
+ const indices = [];
435
+ const dfs = (start) => {
436
+ if (indices.length === k) {
437
+ const subset = indices.map((i) => empties[i]);
438
+ const union = new Set();
439
+ for (const { row, col } of subset) {
440
+ for (const v of this.possiblesGrid[row][col]) {
441
+ union.add(v);
442
+ }
443
+ }
444
+ if (union.size !== k) {
445
+ return null;
446
+ }
447
+ const subsetKeys = new Set(subset.map(({ row, col }) => row * 9 + col));
448
+ const eliminations = [];
449
+ for (const { row, col } of houseCells) {
450
+ if (this.board[row][col] !== 0 || subsetKeys.has(row * 9 + col)) {
451
+ continue;
452
+ }
453
+ const poss = this.possiblesGrid[row][col];
454
+ for (const d of union) {
455
+ if (poss.includes(d)) {
456
+ eliminations.push({ row, col, value: d });
457
+ }
458
+ }
459
+ }
460
+ if (eliminations.length === 0) {
461
+ return null;
462
+ }
463
+ return { type: "elimination", eliminations, algorithm };
464
+ }
465
+ for (let i = start; i < n; i++) {
466
+ indices.push(i);
467
+ const res = dfs(i + 1);
468
+ indices.pop();
469
+ if (res)
470
+ return res;
471
+ }
472
+ return null;
473
+ };
474
+ return dfs(0);
475
+ }
374
476
  findHiddenSingle() {
375
477
  for (let row = 0; row < 9; row++) {
376
478
  const move = this.findHiddenSingleInRow(row);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsabol/sudoku-solver",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript Sudoku solver module with solve, next move, describe, and validate APIs.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",