提问人:elCinturon 提问时间:10/31/2023 更新时间:11/1/2023 访问量:23
使用 JavaScript 预填充数独网格
Prefill Sudoku Grid with JavaScript
问:
我正在用普通的JS写一个小数独游戏。 现在我正在为我的代码而苦苦挣扎。我看到了一些示例,但我无法像其他示例一样调整我的代码。也许我的想法是错误的。 我的主要问题是填充函数的递归调用。我不确定何时再次调用它以及何时返回布尔值。 我尝试了一些不同的方法,但我遇到了无穷无尽的循环或部分填充的网格。
function fillGrid(grid) {
// Run through grid to get next 0 to fill
let filled = grid.every((row, rowIndex) => {
// Row contains 0?
let colIndex = row.indexOf(0);
if (colIndex !== -1) {
let randNumb = randomNumberBetween(1, 9);
// Set random number where first 0 is found
grid[rowIndex][colIndex] = randNumb;
// Check validity
let gridValid = gridIsValid(grid);
// When valid continue with the new set number
if (gridValid) {
return fillGrid(grid);
} else {
// When not valid, reset number and have another try
grid[rowIndex][colIndex] = 0;
return fillGrid(grid);
}
}
});
return filled;
}
该函数采用每个位置为 0 的预填充网格,并应使用拟合数字填充整个网格。 我看到的其他示例是迭代数字 1 - 9 并在每次调用函数时跟踪当前的列和行。我更喜欢立即设置一个随机数并计算下一个字段。
答:
这里有几个问题:
首先,当第一行没有零时,你的回调不会执行语句,所以会被认为是假的。因此,永远不会访问第二排。当当前行中没有零时,您需要这样做,以便继续下一行。every
return
every
return true
every
一旦你解决了这个问题,你很可能会进入一个无限的递归循环。假设到目前为止,您的算法已经填充了网格:
1 2 3 4 5 6 7 8 9
4 5 6 1 2 3 . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
在下一个空闲单元格上,我们没有可以放置的值:值 1 到 6 已在第二行中使用,其余三个值已在右上角的 3x3 块中使用。
这意味着您的代码将卡在以下部分:
} else {
// When not valid, reset number and have another try
grid[rowIndex][colIndex] = 0;
return fillGrid(grid);
}
递归调用将一遍又一遍地进行,却再次发现网格无效,于是又进行了一次递归调用......直到达到堆栈限制。
缺少的是回溯。您确实有“回溯”,您可以在其中撤消最后一步,但在前一个单元格中没有回溯到先前放置的值。
您需要检测您是否已经尝试了某个单元格的所有 9 个值,但其中任何一个都没有成功。当这种情况发生时,你需要替换之前的放置值(即使该操作没有导致明显无效的网格)。这种回溯可能需要回溯多个级别......
纠正此问题的一种方法是创建数字 1 到 9 的随机顺序,然后按该顺序尝试它们。现在你可以知道他们什么时候都不起作用,可以做.这将触发回溯。return false
这是根据该想法改编的代码。我添加了一个实现(它可能与您的实现不同,但可以执行它需要执行的操作):gridIsValid
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function range(a, b) {
return Array.from({length: b - a + 1}, (_, i) => i + a);
}
function gridIsValid(grid) {
function column(colIndex) {
return grid.map(row => row[colIndex]);
}
function block(blockIndex) {
const colIndex = (blockIndex % 3) * 3;
const rowIndex = Math.floor(blockIndex / 3) * 3;
return grid.slice(rowIndex, rowIndex + 3).flatMap(row => row.slice(colIndex, colIndex + 3));
}
function groupIsValid(group) {
group = group.filter(Boolean);
return new Set(group).size === group.length;
}
return [
...grid,
...grid.map((_, i) => column(i)),
...grid.map((_, i) => block(i))
].every(groupIsValid);
}
function fillGrid(grid) {
// Run through grid to get next 0 to fill
const row = grid.find((row) => row.includes(0));
if (!row) return true; // The grid has been filled.
const colIndex = row.indexOf(0);
// Loop over the 9 possible values we can put, in random order
for (let randNumb of shuffle(range(1, 9))) {
// Set random number where first 0 is found
row[colIndex] = randNumb;
// Check validity: When valid continue with the new set number
if (gridIsValid(grid) && fillGrid(grid)) return true; // Found!: get out of recursion
// When not valid, reset number and have another try (iteration)
row[colIndex] = 0;
}
// All possible values for this cell failed... so backtrack!
return false;
}
// Demo run:
const grid = Array.from({length: 9}, () => Array(9).fill(0));
fillGrid(grid);
for (const row of grid) console.log(...row);
评论