JavaScript 数独求解器在某些板上陷入无限循环/不适用于所有板

Posted

技术标签:

【中文标题】JavaScript 数独求解器在某些板上陷入无限循环/不适用于所有板【英文标题】:JavaScript sudoku solver is stuck in infinite loop for some boards/doesn't work for all boards 【发布时间】:2019-03-06 21:45:49 【问题描述】:

我是 *** 的新手。我搜索了相关问题,但似乎没有人回答我的问题。我正在上计算机科学的第一学期,目前正在学习编程 1 课程。我们只在整个课程中使用 javascript,因此到目前为止,JavaScript 是我所知道的唯一编程语言。因此,我对整个编程的直觉或其他方面的理解相当有限。英语也不是我的第一语言,所以请原谅我的错误。

不过,我的问题是:我需要编写一个数独求解器。我实际上已经成功地为数独编写了一个求解器,这被认为是“简单的”。完成它只需要几分之一秒,所以我对结果相当满意。问题是:有一些数独不起作用,即那些被认为是“难”的数独。不用说,求解器需要为所有数独工作。以下是“简单”和“硬”数独板示例的代码 sn-ps,以及我的求解器的代码。我试图尽可能地注释来描述功能,但显然存在一个问题,因为它不会解决困难的问题。它实际上陷入了无限循环。

var easyBoard = [
    [1,0,0,3,0,0,9,5,2],
    [0,4,0,6,0,0,1,0,0],
    [3,0,0,1,0,0,0,0,0],
    [0,6,4,7,2,0,0,1,0],
    [8,7,0,9,0,6,0,2,4],
    [0,2,0,0,8,5,7,6,0],
    [0,0,0,0,0,1,0,0,7],
    [0,0,7,0,0,9,0,4,0],
    [2,3,9,0,0,4,0,0,1]  
];

var hardBoard = [
    [4,0,0,6,0,7,0,8,5],
    [0,0,0,0,0,0,6,0,0],
    [0,0,7,0,0,0,0,0,0],
    [0,5,0,0,0,3,0,0,4],
    [3,7,0,0,0,8,0,0,0],
    [6,0,0,2,0,0,0,0,0],
    [8,0,0,0,0,0,3,1,0],
    [0,3,1,0,4,9,0,0,0],
    [0,0,0,0,0,0,0,0,9]
];


var solve = function (board) 
    var empty = []; // We create an array for the 1x1 squares with no value, so we can call upon them with ease later on.
    for (var i = 0; i < 9; i++) 
        for (var j = 0; j < 9; j++) 
            if (board[i][j] === 0) 
                empty.push([i,j]);
            
        
    
    for (var i = 0; i < empty.length;)  // We check every possible value for all empty 1x1 squares.
        var row = empty[i][0]; // Used for row and 3x3 square checks
        var column = empty[i][1]; // Used for column and 3x3 square checks
        var value = board[row][column] + 1; // We start at 1, because obviously 0 is not a Sudoku value.
        var found = false; // We assume the value is invalid, unless it passes all three tests.
        while (!found && value <= 9)  // As long as the value is invalid, we increase by one until it reaches more than 9.
            var equal = false; // We assume for now that the value is not equal to any other in its row, column or 3x3 square.
            for (var y = 0; y < 9; y++) 
                if (board[row][y] === value) 
                    equal = true;
                
            
            for (var x = 0; x < 9; x++) 
                if (board[x][column] === value) 
                    equal = true;
                
            
            for (var x = 3*Math.floor(row/3); x < 3*Math.floor(row/3)+3; x++) 
                for (var y = 3*Math.floor(column/3); y < 3*Math.floor(column/3)+3; y++) 
                    if (board[x][y] === value) 
                        equal = true;
                    
                
            
            if (!equal)  // If the value is not equal to any other in its row, column or 3x3 square, it is valid.
                found = true; // We have found a valid value, for now.
                board[row][column] = value; // We assign said value to the corresponding board 1x1 square, for now.
                i++; // We then move on to the next empty 1x1 square.
            
            else 
                value++; // If the value is invalid, we simply try the next possible value.
            
        
        if (!found)  // If, from 1 to 9, the value is invalid, it means the one before is invalid.
            board[row][column] = 0; // We then re-assign an empty value to the 1x1 square, before backtracking.
            i--; // We go back to the previous 1x1 square to try a different value.
        
    
;

//   test routines

var clone2 = array => array.slice().map( row=>row.slice());

function easyTest() 
    var board = clone2( easyBoard);
    solve( board);
    console.log( "easy board solution:");
    console.log( board);


function hardTest() 
    var board = clone2( hardBoard);
    solve( board);
    console.log( "hard board solution:");
    console.log( board);
<button type="button" onclick="easyTest()">Easy Test</button>
<button type="button" onclick="hardTest()">Hard Test</button>

该代码适用于第一个,但不适用于第二个。是因为回溯/蛮力算法对于“硬”数独来说不够快吗?是只需要几个小时,还是我的代码中有问题导致第一块板出现问题,而第二块板没有问题?

如果有什么不清楚的地方,我很抱歉,我发誓我已经尝试理解其他类似问题的答案,但它们都包含几个我不知道的概念或运算符/对象或任何东西。如果有人能指出我的代码中的问题,并告诉我是否可以用它来解决第二块板,或者我是否需要另一种方法。

非常感谢您!

P.S.:很多人谈论 JavaScript 中的对象,或面向对象的编程。我不知道这是否相关,但我们还没有看到。

【问题讨论】:

您应该研究一个递归函数,该函数在每个滴答声中只能遍历和处理棋盘的一小部分,并在下一次迭代中使用 setTimout。 我不确定这意味着什么。递归与回溯不是一回事吗?我试图理解:所以,我需要告诉程序只做板子的某个部分,但不能超过这个,并且在有限的时间内?是不是因为代码花费了太多时间并且实际上陷入了死胡同,那里有很多可能性,我应该设置一个计时器来结束它?如果花费这么多时间的部分实际上是正确的方法并导致解决方案怎么办?也许我说的完全没有意义,但我不确定我是否理解你的评论。 我指的是您谈论长执行时间的部分。如果您遇到死胡同,这将无济于事,但它确实绕过了 JS 在各种系统上的限制。一个系统可能会停止,而另一个系统工作正常。 无法重现。问题似乎是大学里的电脑。我已经编辑了代码 sn-p,因此您可以在笔记本电脑或 PC 上实际运行它以查看它的工作情况。我的 64 位但速度较慢的笔记本电脑需要 8 秒才能运行 1 亿次循环迭代,这调用了一个无操作函数——按我的计算,这将增加至少 4 亿个指令步骤 该死,下面给出答案的人说他也花了大约 10 秒。问题是,我现在正在运行求解,它说它的步数略高于 10 亿步……步数与循环迭代相同吗?我不这么认为,因为当我逐步运行时,每次光标在代码中移动时,步进刻度都会增加 1,所以我可能远低于 1 亿次实际循环迭代。另外,教授使用所说的基于服务器的程序来读取代码,所以我需要找到一个可以使用它运行的代码。你知道吗?螺丝蛮力。我会尝试约束传播。 【参考方案1】:

有些不对劲。您发布的代码在 1800 毫秒 内解决了“硬”板。经过一些优化后,我在用于测试的同一台 Windows 笔记本电脑上将时间缩短到了大约 300 毫秒

我在这里提供优化版本来测试uni计算机是否可以运行它如果你想尝试。如果您仍在使用蛮力解决方案,我非常谨慎建议这样做,但它肯定是您的代码的一个版本!

最后,您可以测试 uni 计算机是否根本不允许蛮力算法有足够的时间来完成(或在它运行的自定义 JS 引擎中实现指令“步骤”限制)。

function emptyCells( board) 
    var empty = [];
    for (var i = 0; i < 9; i++) 
        for (var j = 0; j < 9; j++) 
            if (board[i][j] === 0) 
                var boxRow = 3* Math.floor( i/3);
                var boxCol = 3* Math.floor( j/3);
                empty.push([i,j, boxRow, boxCol]);
            
        
    
    return empty;


function isUnique( board, empty, value) 
    var row, col;

    // test row
    row = board[empty[0]];
    for( col = 0; col < 9; ++ col) 
        if( value == row[col]) 
            return false;
        
    
    // test col
    col = empty[1];
    for( var row = 0; row < 9; ++row) 
        if( value == board[ row][col])
            return false;
        	
    
    // test box
    var boxRow = empty[2];
    var boxCol = empty[3];
    for( var i = 3; i--;) 
        row = board[ boxRow++];
        for( var j = 3; j--;) 
            if( row[boxCol + j] == value) 
                return false;
            
        
    
    return true;


var solve = function (board) 
    var empty = emptyCells( board);

    nextEmpty:
    for (var i = 0; i < empty.length;)  // We check every possible value for all empty 1x1 squares.
        var row = empty[i][0]; // Used for row and 3x3 square checks
        var column = empty[i][1]; // Used for column and 3x3 square checks
        var value = board[row][column] + 1; // We start at 1, because obviously 0 is not a Sudoku value.   
        var cell = empty[i];

        while (value <= 9)  // test values up to 9.
            if( isUnique( board, cell, value)) 
                board[row][column] = value; // We assign said value to the corresponding board 1x1 square, for now.
                i++; // Move on to the check next empty cell.
                continue nextEmpty;
            
            value++; // If the value is invalid, we simply try the next possible value.    
        

        board[row][column] = 0;
        if( i == 0)   // board is not solvable
            return null;
        
        i--; // We go back to the previous 1x1 square to try a different value.
    
    return board;
;
var board = [
    [4,0,0,6,0,7,0,8,5],
    [0,0,0,0,0,0,6,0,0],
    [0,0,7,0,0,0,0,0,0],
    [0,5,0,0,0,3,0,0,4],
    [3,7,0,0,0,8,0,0,0],
    [6,0,0,2,0,0,0,0,0],
    [8,0,0,0,0,0,3,1,0],
    [0,3,1,0,4,9,0,0,0],
    [0,0,0,0,0,0,0,0,9]
];
var t0 = Date.now();
solve(board);
var t1 = Date.now();
console.log( " in " + (t1-t0) + "ms");
console.log( board.map( row=> row.join(',')).join('\n'));
console.log( "\n solved in " + (t1-t0) + "ms");

【讨论】:

是的,出事了。我相信“uni 计算机根本不允许蛮力算法有足够的时间来完成(或在它运行的自定义 JS 引擎中实现指令“步骤”限制)”。它必须是故意的。他们知道我们想走简单的路并使用蛮力。 ;) 无论如何,非常感谢您的帮助,至少您尝试过,它最终可能为我节省了一些宝贵的时间!【参考方案2】:

大约 10 秒后,第二个问题在我的计算机上得到解决

[4, 9, 3, 6, 1, 7, 2, 8, 5]
[2, 8, 5, 9, 3, 4, 6, 7, 1]
[1, 6, 7, 8, 5, 2, 4, 9, 3]
[9, 5, 8, 1, 6, 3, 7, 2, 4]
[3, 7, 2, 4, 9, 8, 1, 5, 6]
[6, 1, 4, 2, 7, 5, 9, 3, 8]
[8, 4, 9, 5, 2, 6, 3, 1, 7]
[5, 3, 1, 7, 4, 9, 8, 6, 2]
[7, 2, 6, 3, 8, 1, 5, 4, 9]

【讨论】:

真的吗?你用我的代码在 10 秒内解决了它?只是为了好玩,您可以告诉我程序已采取的“步骤”数量吗?我们使用我们大学开发的基于服务器的 JavaScript 阅读器,但实验室中的计算机与您的计算机相比可能非常慢。当我加载程序时,它在大约一分钟内获得了 1 亿步。我不知道这是否非常缓慢,如果这就是我无法解决数独的原因。你觉得和电脑性能有关系吗?为了清楚起见,您是否真的使用我的代码来做到这一点? 是的,我使用了你的代码。 866396 步为难的。 真正的****是什么?那为什么我的电脑能达到 10 亿步呢? XD 是关于记忆还是什么?就像,不仅仅是处理能力......我不知道你是否明白我的意思......它也应该花费我 866396 步,只是可能多一点时间。我目前正在研究约束传播/消除过程功能来规避问题,但如果我能理解为什么我的蛮力不适用于第二个功能,那就太好了,这意味着我可以使用它!我将使用上面那个人所做的更新代码。 是的,就像上面那个人说的,这可能是“uni 计算机根本不允许蛮力算法有足够的时间来完成(或在它运行的自定义 JS 引擎中实现指令“步骤”限制) ”。混蛋知道我们想做回溯而不是约束传播。他们知道我们的一举一动

以上是关于JavaScript 数独求解器在某些板上陷入无限循环/不适用于所有板的主要内容,如果未能解决你的问题,请参考以下文章

无法回溯以使用递归 javascript 数独求解器

在尝试用 Javascript 实现数独求解器时,我的逻辑哪里出了问题?

数独回溯 无效数独

JavaScript - 最大的公约数 - 陷入无限循环

Visual Studio 2015 JavaScript 语言服务陷入无限循环下载不存在的引用文件

数独游戏求解程序