Sudoku solver in JS

夙愿已清 提交于 2021-01-21 05:11:13

问题


I'm trying to write an algorithm that can solve sudoku. For now, my code works till supplyGrid is out of numbers. When it happens it should go back and try another number, right? To be honest I have no clue how to achive that.

var grid = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0]
],
    supplyGrid = [1, 2, 3, 4, 5, 6, 7, 8, 9],
    row = 0,
    col = 0,
    value = 0,
    index = 0;


var solveSudoku = function (grid, row, col) {
    if (col > 8) {
        row++;
        col = 0;
        if (row > 8 && col > 8) {
            console.log(grid);
            return;
        }
    }
    if (grid[row][col] === 0) { //
        index = Math.floor(Math.random() * supplyGrid.length);
        value = supplyGrid[index];
        if (isValid(row, col, value)) {
            grid[row][col] = value;
            col++;
            supplyGrid = [1, 2, 3, 4, 5, 6, 7, 8, 9];
            solveSudoku(grid, row, col);
        } else {
            supplyGrid.splice(index, 1);
            console.log(supplyGrid);
            if (supplyGrid.length < 1) { 
                //backtrack();
                console.log('Out of numbers');
                return;
            }
            solveSudoku(grid, row, col);
        }
    } else { //row = 3, col = 5
        solveSudoku(grid, row, ++col);
    }
    return this;
}

function isValid(row, col, value) {
    if ((validateColumn(row, col, value)) || (validateRow(row, col, value)) || (validateBox(row, col, value))) {
        return false;
    } else {
        return true;
    }
}

function validateBox(row, col, value) {
    row = Math.floor(row / 3) * 3;
    col = Math.floor(col / 3) * 3;
    var isFound = false;
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            if (grid[row + i][col + j] == value) isFound = true;
        }
    }
    return isFound;
}

function validateRow(row, col, value) { 
    var isFound = false;
    for (var i = 0; i < 9; i++) {
        if (grid[row][i] === value) isFound = true;
    }
    return isFound;
}

function validateColumn(row, col, value) { 
    var isFound = false;
    for (var i = 0; i < 9; i++) {
        if (grid[i][col] === value) isFound = true;
    }
    return isFound;
}

回答1:


I solved this question with backtracking algorithm:

const _board = [
    ['.', '9', '.', '.', '4', '2', '1', '3', '6'],
    ['.', '.', '.', '9', '6', '.', '4', '8', '5'],
    ['.', '.', '.', '5', '8', '1', '.', '.', '.'],
    ['.', '.', '4', '.', '.', '.', '.', '.', '.'],
    ['5', '1', '7', '2', '.', '.', '9', '.', '.'],
    ['6', '.', '2', '.', '.', '.', '3', '7', '.'],
    ['1', '.', '.', '8', '.', '4', '.', '2', '.'],
    ['7', '.', '6', '.', '.', '.', '8', '1', '.'],
    ['3', '.', '.', '.', '9', '.', '.', '.', '.'],
];
sodokoSolver(_board);
console.log(_board);

function isValid(board, row, col, k) {
    for (let i = 0; i < 9; i++) {
        const m = 3 * Math.floor(row / 3) + Math.floor(i / 3);
        const n = 3 * Math.floor(col / 3) + i % 3;
        if (board[row][i] == k || board[i][col] == k || board[m][n] == k) {
          return false;
        }
    }
    return true;
}


function sodokoSolver(data) {
  for (let i = 0; i < 9; i++) {
    for (let j = 0; j < 9; j++) {
      if (data[i][j] == '.') {
        for (let k = 1; k <= 9; k++) {
          if (isValid(data, i, j, k)) {
            data[i][j] = `${k}`;
          if (sodokoSolver(data)) {
           return true;
          } else {
           data[i][j] = '.';
          }
         }
       }
       return false;
     }
   }
 }
 return true;
}



回答2:


Here is an open source repository which has provided a javascript solution:

https://github.com/pocketjoso/sudokuJS

Here is the code which details his solution:

https://github.com/pocketjoso/sudokuJS/blob/master/sudokuJS.js




回答3:


I came up with this solution check this out:

function sudoku(grid: number[][]): boolean {
  const rows: Set<number>[] = [];
  const cols: Set<number>[] = [];
  const miniGrids: Set<number>[] = [];

  for (let i = 0; i < 9; ++i) {
    rows.push(new Set());
    cols.push(new Set());
    miniGrids.push(new Set());
  }

  for (let i = 0; i < 9; i++) {
    for (let j = 0; j < 9; j++) {
      const gridId: number = Math.trunc(i / 3) * 3 + Math.trunc(j / 3);
      const n: number = grid[i][j];
      if (rows[i].has(n) || cols[j].has(n) || miniGrids[gridId].has(n)) return false;

      rows[i].add(n);
      cols[j].add(n);
      miniGrids[gridId].add(n);
    }
  }

  return true;
}



回答4:


Here is a simpler way. You basically use a hash object to keep track of the rows, columns and sub-boxes.

const _board = [
    ['.', '9', '.', '.', '4', '2', '1', '3', '6'],
    ['.', '.', '.', '9', '6', '.', '4', '8', '5'],
    ['.', '.', '.', '5', '8', '1', '.', '.', '.'],
    ['.', '.', '4', '.', '.', '.', '.', '.', '.'],
    ['5', '1', '7', '2', '.', '.', '9', '.', '.'],
    ['6', '.', '2', '.', '.', '.', '3', '7', '.'],
    ['1', '.', '.', '8', '.', '4', '.', '2', '.'],
    ['7', '.', '6', '.', '.', '.', '8', '1', '.'],
    ['3', '.', '.', '.', '9', '.', '.', '.', '.'],
];

function isValidSudoku(grid) {
    let seenRow = {},
        seenCol = {},
        seenSubBox = {},
        seen = {}

    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            let value = grid[row][col];
            if (!(value === '.')) {
                let rowKey = `${row}-${value}`,
                    colKey = `${col}-${value}`,
                    boxKey = `${Math.floor(row/3)}-${value}-${Math.floor(col/3)}`

                if (seenRow[rowKey] || seenCol[colKey] || seenSubBox[boxKey]) {
                    return false;
                }               
                seenRow[rowKey] = true;
                seenCol[colKey] = true;
                seenSubBox[boxKey] = true;
            }
        }
    }

    return true;
}

console.log(isValidSudoku(_board));



回答5:


I have written a solution to solve any squared sudoku since the answers solve only 9x9 squared.

https://github.com/Eomm/grokking/blob/master/games/sudoku/play-sudoku.js

The algorithm is always the backtracking one, but it is optimized to reduce the iterations (aka time) ignoring the default cells and using stochastic statistics to choose the value (aka random :) ).

Compared to the first answer that iterates always 768.028 times, this solution runs from 3.556 (very lucky run) to 31.824 (not so lucky) with an average of 19.592 iterations in 100 tests. Comparison done with the same input of course.

Here the code:

const gameSet = [
  new Uint8Array([0, 0, 1, 5, 0, 0, 0, 7, 0]),
  new Uint8Array([0, 0, 4, 0, 6, 0, 0, 0, 9]),
  new Uint8Array([0, 3, 0, 0, 0, 4, 0, 0, 0]),
  new Uint8Array([6, 2, 0, 0, 0, 5, 1, 0, 0]),
  new Uint8Array([0, 4, 0, 0, 0, 0, 5, 2, 0]),
  new Uint8Array([0, 0, 0, 0, 4, 8, 0, 0, 3]),
  new Uint8Array([4, 1, 0, 0, 7, 0, 0, 0, 0]),
  new Uint8Array([0, 0, 6, 8, 0, 0, 0, 0, 1]),
  new Uint8Array([8, 0, 0, 0, 0, 9, 0, 3, 0])
]

process.nextTick(() => {
  const sudoku = new Sudoku({ squareRegion: 3 })
  sudoku.play(gameSet)
  console.log(sudoku.printable())
})

function Sudoku (opts = {}) {
  // TODO improve to support different sudoku types
  this.region = opts.squareRegion || 3 // default classic
}

Sudoku.prototype.play = function (gameSet) {
  const allCells = buildCellStructure(gameSet, this.region)

  this.valueSet = Array(gameSet[0].length).fill(0).map((_, i) => (i + 1))

  // to reduce the calculation, we can just ignore the default game cells
  const cells = allCells.filter(c => c.init === 0)
  let iter = 0
  for (let i = 0; i < cells.length; i++) {
    const cell = cells[i]
    iter++
    if (!solveCell.call(this, cell)) {
      cell.history.clear() // out tries are invalid

      let backTrack = i - 1
      for (; backTrack >= 0; backTrack--) {
        if (assignValue.call(this, cells[backTrack], 0)) {
          break
        }
      }
      i = backTrack - 1
    }
  }

  this.lastGame = gameSet
  this.lastResult = allCells.map(_ => _.value)

  console.log(iter)
  return this.lastResult
}

function solveCell (cell) {
  const chooseNewValue = chooseValue.call(this, cell)
  if (chooseNewValue === 0) {
    return false
  }
  assignValue.call(this, cell, chooseNewValue)
  return true
}

function assignValue (cell, value) {
  cell.rows[cell.x] = value
  cell.columns[cell.y] = value
  cell.square[(cell.x % this.region) + ((cell.y % this.region) * this.region)] = value
  cell.value = value

  if (value > 0) {
    cell.history.add(value)
  }
  return true
}

Sudoku.prototype.printable = function (result) {
  const print = result || this.lastResult
  if (!print) {
    // nothing to print
    return
  }

  return print.flatMap((val, i) => {
    if ((i + 1) % this.region === 0) {
      if ((i + 1) % (this.region ** 2) === 0) {
        if ((i + 1) % (this.region ** 3) === 0) {
          return [val, '|', '\n', '-'.repeat(this.region ** 2), '--|\n']
        } else {
          return [val, '|', '\n']
        }
      } else {
        return [val, '|']
      }
    }
    return val
  }).join('')
}

function chooseValue (cell) {
  const values = this.valueSet
    .filter(_ => !cell.rows.includes(_))
    .filter(_ => !cell.columns.includes(_))
    .filter(_ => !cell.square.includes(_))
    .filter(_ => !cell.history.has(_))
  if (values.length === 0) {
    return 0
  }
  // stochastic hope
  return values[Math.floor(Math.random() * values.length)]
}

// this structure point always to the same arrays
function buildCellStructure (gameSet, squareLength) {
  const cells = []

  const columnMap = new Map()
  const squareMap = new Map()

  gameSet.forEach((row, y) => {
    row.forEach((cellValue, x) => {
      if (!columnMap.has(x)) {
        columnMap.set(x, [])
      }
      columnMap.get(x).push(cellValue)

      const squareId = `${Math.floor(x / squareLength)}-${Math.floor(y / squareLength)}`
      if (!squareMap.has(squareId)) {
        squareMap.set(squareId, [])
      }
      squareMap.get(squareId).push(cellValue)

      cells.push({
        x,
        y,
        value: cellValue,
        init: cellValue,
        rows: row,
        columns: columnMap.get(x),
        squareId,
        square: squareMap.get(squareId),
        history: new Set(),
        iter: 0
      })
    })
  })

  return cells
}




回答6:


using backtrack and search. also, you can visualise your solved board little better. Check this out.

const b = null;

//test cases

const bd1 = [
    [1, b, b, b, b, b, b, b, 7],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, 5, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, 1, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, 2, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, 6],
] 

const bd2 = [
    [1, b, 5, b, b, b, b, b, 3],
    [3, b, b, b, b, b, b, b, b],
    [b, b, b, b, 8, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, 4, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, 3, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, 9],
]

const bd3 = [
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
    [b, b, b, b, b, b, b, b, b],
] 

//words toughest 2012
const bdTf = [
    [8, b, b, b, b, b, b, b, b],
    [b, b, 3, 6, b, b, b, b, b],
    [b, 7, b, b, 9, b, 2, b, b],
    [b, 5, b, b, b, 7, b, b, b],
    [b, b, b, b, 4, 5, 7, b, b],
    [b, b, b, 1, b, b, b, 3, b],
    [b, b, 1, b, b, b, b, 6, 8],
    [b, b, 8, 5, b, b, b, 1, b],
    [b, 9, b, b, b, b, 4, b, b],
] 



function solveSudoku(board) {
    if (solveSudokud(board)) {
        return board
    } else {
        const possibilities = nextBoards(board)
        const validBoards = keepOnlyValid(possibilities) //filterFunction
        return searchForSolution(validBoards) //helperFunction :backtracking
    }
}

function searchForSolution(boards) {    
    if (boards.length < 1) {        //checking the board is empty
        return false
    } else {        
        var first = boards.shift()  //tak̉es the first board off and stores in the variable
        const tryPath = solveSudoku(first)   
        if (tryPath != false) { //checking if tryPath is false or not. if it is not false, we wil return the solved board
            return tryPath
        } else {
            return searchForSolution(boards)    
        }        
    }
}
function solveSudokud(board) {
    for (var i = 0; i < 9; i++) {       
        for (var j = 0; j < 9; j++) {
            if (board[i][j] === null) {
                return false
            }
        }
    }
    return true
}
//nextBoardFunction will generate 9 possiblities for a pissible sudoku board
function nextBoards(board) { 
    var res = [];       
    const firstEmpty = findEmptySquare(board) 
    if (firstEmpty != undefined) {     //if firstEmpty = not defined , then we will start generating possiblities 
        const y = firstEmpty[0]     
        const x = firstEmpty[1]
        for (var i = 1; i < 10; i++) {
            var newBoard = [...board]
            var row = [...newBoard[y]]
            row[x] = i
            newBoard[y] = row
            res.push(newBoard)
        }
    }
    return res // if firstEmpty does =  undefined that means there are no possibilities left and return res.
}

function findEmptySquare(board) {
    // board --> [int, int] | represents the x and y coordinates of the empty sq
    for (var i = 0; i < 9; i++) {
        for (var j = 0; j < 9; j++) {
            if (board[i][j] == null) {
                return [i, j]
            }
        }
    }
}
//filter funtion
function keepOnlyValid(boards) {
    return boards.filter(b => validBoard(b))    //filtered version of the board array will be returned
}   
function validBoard(board) {
    
    // check each row
    for(let i=0; i<9; i++) {
        if(!validate(board[i])) return false
    }
    //check each col
    for(let i=0; i<9; i++) {
        var arr = []
        for(let j=0; j<9; j++) {
            arr.push(board[j][i]);
        }
        if(!validate(arr)) return false;
    }
    //check each 3*3
    let row = [[0,1,2], [3,4,5], [6,7,8]]
    let col = [[0,1,2], [3,4,5], [6,7,8]]
    for(let i of row) {
        for(let j of col) {
            let arr = [];
            for(let num1 of i) {
                for(let num2 of j){
                    arr.push(board[num1][num2]);
                }
            }
            if(!validate(arr)) return false;
        }
    }
    return true
}



function validate(arr) {
    //check duplicates
    let set1 = new Set();
    for(let i=0; i< arr.length; i++) {
        if(arr[i] === b) continue;
        if(set1.has(arr[i])) return false
        set1.add(arr[i]);
    }
    return true
}


//for better visualisation and able to check manually

function get_row(board, row) {
    
    return board[row]
    
}
function print_cell(value) {
    if (Array.isArray(value)) {
        return '.'
    } else if (value == null) {
        return '.'
    } else {
        return value
    }
}
function print_board(board) {
   
    console.log()
    for (i = 0; i < 9; i++) {
        let row = get_row(board, i)
        if (i % 3 == 0) {
            console.log("|=======|=======|=======|")
        }
        console.log("|",
            print_cell(row[0]), print_cell(row[1]), print_cell(row[2]), "|",
            print_cell(row[3]), print_cell(row[4]), print_cell(row[5]), "|",
            print_cell(row[6]), print_cell(row[7]), print_cell(row[8]), "|")
    }
    console.log("|=======|=======|=======|")
}

console.log('testcase 1')
print_board(bd1)
print_board(solveSudoku(bd1))
console.log('testcase 2')
print_board(bd2)
print_board(solveSudoku(bd2))
console.log('testcase 3')
print_board(bd3)
print_board(solveSudoku(bd3))
console.log('testcase 4')
print_board(bdTf)
print_board(solveSudoku(bdTf))


来源:https://stackoverflow.com/questions/42736648/sudoku-solver-in-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!