Cell = number instead of object, return flat array
This commit is contained in:
parent
c16a68e796
commit
a66a20b647
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
let line = "";
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
if (i > 0 && i % side == 0) {
|
||||||
|
console.log(line);
|
||||||
|
line = "";
|
||||||
|
|
||||||
puzzle.forEach((row, i) => {
|
if (i > 0 && Math.floor(i / side) % height == 0) {
|
||||||
let line = "";
|
let divider = "";
|
||||||
row.forEach(({ value: cell }, j) => {
|
Array(width * height)
|
||||||
if (j > 0 && j % width == 0) {
|
.fill(null)
|
||||||
line += "| ";
|
.forEach((_, j) => {
|
||||||
|
if (j > 0 && j % width == 0) {
|
||||||
|
divider += " ";
|
||||||
|
}
|
||||||
|
divider += "---";
|
||||||
|
});
|
||||||
|
console.log(divider);
|
||||||
}
|
}
|
||||||
line += ((cell ? cell : " ") + " ").padStart(3, " ");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (i > 0 && i % height == 0) {
|
|
||||||
let divider = "";
|
|
||||||
row.forEach((_, j) => {
|
|
||||||
if (j > 0 && j % width == 0) {
|
|
||||||
divider += " ";
|
|
||||||
}
|
|
||||||
divider += "-- ";
|
|
||||||
});
|
|
||||||
console.log(divider);
|
|
||||||
}
|
}
|
||||||
console.log(line);
|
|
||||||
|
if (i % side !== 0 && i % width === 0) {
|
||||||
|
line += "| ";
|
||||||
|
}
|
||||||
|
|
||||||
|
line += (cell > 0 ? `${cell}` : "").padStart(2, " ") + " ";
|
||||||
});
|
});
|
||||||
|
console.log(line);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user