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

时间:2019-03-06 21:45:50

标签: javascript backtracking sudoku brute-force

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

不过,这是我的问题:我需要对数独求解器进行编程。实际上,我已经成功地为数独(sudoku)编码了一个求解器,这被认为是“简单”的。它花费了不到一秒钟的时间,所以我对结果很满意。问题是:有些数独不起作用,即那些被认为是“硬”的数独。不用说,求解器需要为所有数独工作。以下是“简易”和“硬”数独板示例的代码片段,以及我的求解器的代码。我试图尽我最大的能力来描述功能,但是显然存在一个问题,因为它无法解决困难的问题。实际上它陷入了无限循环。

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内的对象或面向对象的程序设计。我不知道它是否相关,但是我们还没有看到任何一个。

2 个答案:

答案 0 :(得分:0)

第二个问题在约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]

答案 1 :(得分:0)

某事不正确。您发布的代码在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");