Cell = number instead of object, return flat array

This commit is contained in:
Matt Low 2021-02-23 18:19:57 -07:00
parent c16a68e796
commit a66a20b647
4 changed files with 53 additions and 60 deletions

View File

@ -2,9 +2,6 @@ import { gql } from "../mods";
import { generate, GenerateArguments } from "../sudoku/index"; import { generate, GenerateArguments } from "../sudoku/index";
export const typeDefs = gql` export const typeDefs = gql`
type Cell {
value: Int
}
""" """
A sudoku A sudoku
""" """
@ -19,7 +16,7 @@ export const typeDefs = gql`
size: Int! size: Int!
"The rows of the board, from top to bottom." "The rows of the board, from top to bottom."
cells: [[Cell!]!]! cells: [Int!]!
} }
type Query { type Query {

View File

@ -1,9 +1,10 @@
import { StaticPool, isTimeoutError } from "node-worker-threads-pool"; import { StaticPool, isTimeoutError } from "node-worker-threads-pool";
import WORKERS from "physical-cpu-count"; import WORKERS from "physical-cpu-count";
import { prettyPrint } from "./util";
const TIMEOUT = 20000; const TIMEOUT = 20000;
export type Cell = { value: number }; export type Cell = number;
export type GenerateArguments = { export type GenerateArguments = {
regionWidth: number; regionWidth: number;
@ -15,10 +16,10 @@ export type Sudoku = {
regionWidth: number; regionWidth: number;
regionHeight: number; regionHeight: number;
size: number; size: number;
cells: Cell[][]; cells: Cell[];
}; };
const pool = new StaticPool<GenerateArguments, Cell[][]>({ const pool = new StaticPool<GenerateArguments, Cell[]>({
size: WORKERS, size: WORKERS,
task: "./src/sudoku/worker.js", task: "./src/sudoku/worker.js",
}); });
@ -44,6 +45,8 @@ export async function generate(
TIMEOUT TIMEOUT
); );
prettyPrint(regionWidth, regionHeight, puzzle);
return { return {
regionWidth, regionWidth,
regionHeight, regionHeight,

View File

@ -11,6 +11,7 @@ import { shuffle, range } from "./util";
import { Cell } from "./index"; import { Cell } from "./index";
type NodeMeta = { type NodeMeta = {
index: number;
row: number; row: number;
col: number; col: number;
region: number; region: number;
@ -73,11 +74,11 @@ export class SudokuMath {
Math.floor(row / this.regionHeight) * this.regionHeight + Math.floor(row / this.regionHeight) * this.regionHeight +
Math.floor(col / this.regionWidth); Math.floor(col / this.regionWidth);
const val = candidate % this.values; const val = candidate % this.values;
return [candidate, row, col, region, val]; return [boardIndex, row, col, region, val];
} }
_checkInput(cells: Cell[][]) { _checkInput(cells: Cell[]) {
if (cells.length !== this.values || cells[0].length !== this.values) { if (cells.length !== this.values2) {
throw new Error( throw new Error(
"Given cells array does not match regionWidth & regionHeight" "Given cells array does not match regionWidth & regionHeight"
); );
@ -86,7 +87,7 @@ export class SudokuMath {
// this takes a bit of time and the value may need to be cached // this takes a bit of time and the value may need to be cached
getDLXHeader( getDLXHeader(
cells: undefined | Cell[][] = undefined, cells: undefined | Cell[] = undefined,
randomSearch = false randomSearch = false
): [CNode, DNode[]] { ): [CNode, DNode[]] {
if (cells) this._checkInput(cells); if (cells) this._checkInput(cells);
@ -111,9 +112,9 @@ export class SudokuMath {
: this.candidates; : this.candidates;
const dlxRows: DNode[] = []; const dlxRows: DNode[] = [];
candidates.forEach(([i, row, col, region, val]) => { candidates.forEach(([boardIndex, row, col, region, val]) => {
if (cells) { if (cells) {
const exist = cells[row][col].value; const exist = cells[boardIndex];
if (exist && exist - 1 !== val) { if (exist && exist - 1 !== val) {
// skip candidates matching this constraint's position, but not its value // skip candidates matching this constraint's position, but not its value
// the effect is the exisitng value is preserved in the output // the effect is the exisitng value is preserved in the output
@ -121,7 +122,7 @@ export class SudokuMath {
} }
} }
const meta = { row, col, region, value: val + 1 }; const meta = { index: boardIndex, row, col, region, value: val + 1 };
const dlxRow = linkNodesLR( const dlxRow = linkNodesLR(
this.getConstraintIDs(val, row, col, region).map((id) => this.getConstraintIDs(val, row, col, region).map((id) =>
addNodeToColumn(constraints[id], meta) addNodeToColumn(constraints[id], meta)
@ -133,20 +134,11 @@ export class SudokuMath {
return [header, dlxRows]; return [header, dlxRows];
} }
_baseBoard(): Cell[][] { _baseBoard(): Cell[] {
// return a sudoku board with a random set of values in the first row // return a sudoku board with a random set of values in the first row
// used in generateComplete for small speedup // used in generateComplete for small speedup
const firstRow = shuffle(range(1, this.values + 1)); const firstRow = shuffle(range(1, this.values + 1));
return [ return [...firstRow, ...Array(this.values2 - this.values).fill(0)];
firstRow.map((value) => ({ value })),
...Array(this.values - 1)
.fill(null)
.map(() =>
Array(this.values)
.fill(0)
.map((value) => ({ value }))
),
];
} }
generateComplete() { generateComplete() {
@ -156,7 +148,7 @@ export class SudokuMath {
const callback = (solution: DNode[]) => { const callback = (solution: DNode[]) => {
solution.forEach((node) => { solution.forEach((node) => {
const meta: NodeMeta = node.meta; const meta: NodeMeta = node.meta;
result[meta.row][meta.col] = { value: meta.value }; result[meta.index] = meta.value;
}); });
// return the first solution // return the first solution
return true; return true;
@ -176,12 +168,12 @@ export class SudokuMath {
let solutions = 0; let solutions = 0;
const dlx = new DLX(header, () => ++solutions >= 2); const dlx = new DLX(header, () => ++solutions >= 2);
const candidates: DNode[][][] = Array.from(Array(this.values), () => const candidates: DNode[][] = Array.from(Array(this.values2), () =>
Array.from(Array(this.values), () => Array(this.values)) Array.from(Array(this.values))
); );
dlxRows.forEach((node) => { dlxRows.forEach((node) => {
const meta = node.meta; const meta = node.meta;
candidates[meta.row][meta.col][meta.value - 1] = node; candidates[meta.index][meta.value - 1] = node;
}); });
// board positions which have been removed // board positions which have been removed
@ -200,12 +192,9 @@ export class SudokuMath {
if (removed.has(n)) { if (removed.has(n)) {
continue; continue;
} }
const row = Math.floor(n / this.values); const nodes = candidates[n];
const col = n % this.values;
const existValue = completed[row][col].value;
const nodes = candidates[row][col];
nodes.forEach((node) => { nodes.forEach((node) => {
if (node.meta.value !== existValue) { if (node.meta.value !== completed[n]) {
masked.push(node); masked.push(node);
maskRow(node); maskRow(node);
} }
@ -257,17 +246,17 @@ export class SudokuMath {
} }
removed.forEach((index) => { removed.forEach((index) => {
completed[Math.floor(index / this.values)][index % this.values].value = 0; completed[index] = 0;
}); });
return completed; return completed;
} }
solve(existing: Cell[][]): void { solve(existing: Cell[]): void {
const [header] = this.getDLXHeader(existing); const [header] = this.getDLXHeader(existing);
const callback = (solution: DNode[]) => { const callback = (solution: DNode[]) => {
solution.forEach((node) => { solution.forEach((node) => {
const meta: NodeMeta = node.meta; const meta: NodeMeta = node.meta;
existing[meta.row][meta.col] = { value: meta.value }; existing[meta.index] = meta.value;
}); });
// return the first solution // return the first solution
return true; return true;

View File

@ -48,39 +48,43 @@ export function mapArray(arr: any[], indexes: number[]) {
} }
export function clone(puzz: Cell[]): Cell[] { export function clone(puzz: Cell[]): Cell[] {
return puzz.map(({ value }) => ({ value })); return Array.from(puzz);
} }
export function addCellValuesToSet(set: Set<number>, cells: Cell[]) { export function addCellValuesToSet(set: Set<number>, cells: Cell[]) {
cells.forEach((cell) => { cells.forEach((cell) => {
if (!!cell.value) set.add(cell.value); if (cell > 0) set.add(cell);
}); });
return set; return set;
} }
export function prettyPrint(puzzle: Cell[][]) { export function prettyPrint(width: number, height: number, cells: Cell[]) {
let width = Math.sqrt(puzzle[0].length); const side = width * height;
let height = Math.sqrt(puzzle.length);
puzzle.forEach((row, i) => {
let line = ""; let line = "";
row.forEach(({ value: cell }, j) => { cells.forEach((cell, i) => {
if (j > 0 && j % width == 0) { if (i > 0 && i % side == 0) {
line += "| "; console.log(line);
} line = "";
line += ((cell ? cell : " ") + " ").padStart(3, " ");
});
if (i > 0 && i % height == 0) { if (i > 0 && Math.floor(i / side) % height == 0) {
let divider = ""; let divider = "";
row.forEach((_, j) => { Array(width * height)
.fill(null)
.forEach((_, j) => {
if (j > 0 && j % width == 0) { if (j > 0 && j % width == 0) {
divider += " "; divider += " ";
} }
divider += "-- "; divider += "---";
}); });
console.log(divider); console.log(divider);
} }
console.log(line); }
if (i % side !== 0 && i % width === 0) {
line += "| ";
}
line += (cell > 0 ? `${cell}` : "").padStart(2, " ") + " ";
}); });
console.log(line);
} }