使用 JavaScript 预填充数独网格

Prefill Sudoku Grid with JavaScript

提问人:elCinturon 提问时间:10/31/2023 更新时间:11/1/2023 访问量:23

问:

我正在用普通的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 并在每次调用函数时跟踪当前的列和行。我更喜欢立即设置一个随机数并计算下一个字段。

JavaScript 回溯 数独

评论


答:

0赞 trincot 11/1/2023 #1

这里有几个问题:

首先,当第一行没有零时,你的回调不会执行语句,所以会被认为是假的。因此,永远不会访问第二排。当当前行中没有零时,您需要这样做,以便继续下一行。everyreturneveryreturn trueevery

一旦你解决了这个问题,你很可能会进入一个无限的递归循环。假设到目前为止,您的算法已经填充了网格:

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);

评论

0赞 trincot 11/3/2023
对这个答案有什么反馈吗?