0

I have written a function that generates a valid random sudoku board. Although, I am confused on why I need the i loop.

If I remove this loop my program no longer runs correctly. Once it hits an invalid position (backtracks) it ends all together.

In most implementations, the i is checked to see if it can be positioned at the free square. Although, I am using random numbers and not using this i var.

Therefore why do I need this loop? Does my program return back to the iteration when backtracking?

go playground demo

// Creates a valid random BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
  // get the next available pos on board
  freePos := board.FreePos()
  if freePos == nil {
    // no more positions, done
    return true
  }

  freeRow := freePos[0]
  freeCol := freePos[1]

  // generate random number
  randNum := rand.Intn(10-1) + 1
  rand.Seed(time.Now().UnixNano())

  // this i loop is the confusion?
  for i := 0; i <= 8; i++ {

    // check if we can place at freePos
    if board.ValidPos(randNum, freeRow, freeCol) {
      // valid, place
      board.BoardArray[freeRow][freeCol] = randNum

      // recursion
      if board.GenerateBoard(freeRow, freeCol + 1) {
        return true
      } else {
        // backtrack, set back to 0
        board.BoardArray[freeRow][freeCol] = 0
      }
    }
  }
  return false
}

board/board.go

package board

import (
    "fmt"
    "math/rand"
    "time"
)

type Board struct {
    BoardArray [9][9]int
}

// Creates a valid random BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
    // get the next available pos on board
    freePos := board.FreePos()
    if freePos == nil {
        // no more positions, done
        return true
    }

    freeRow := freePos[0]
    freeCol := freePos[1]

    // generate random number
    randNum := rand.Intn(10-1) + 1
    rand.Seed(time.Now().UnixNano())

    for i := 0; i <= 8; i++ {
        if board.ValidPos(randNum, freeRow, freeCol) {
            board.BoardArray[freeRow][freeCol] = randNum

            if board.GenerateBoard(freeRow, freeCol + 1) {
                return true
      } else {
        board.BoardArray[freeRow][freeCol] = 0
      }
        }
    }
    return false
}

// Solves BoardArray (existing board)
func (board *Board) SolveBoard(cellVal int, row int, col int) bool {
    // get the next available pos on board
    freePos := board.FreePos()
    if freePos == nil {
        // no more positions, done
        return true
    }

    freeRow := freePos[0]
    freeCol := freePos[1]

    for i := 1; i <= 9; i++ {
        // board.PrintBoard()
        // fmt.Printf("Checking: row:%d,col:%d,val:%d \n", freeRow, freeCol, i)
        if board.ValidPos(i, freeRow, freeCol) {
            // fmt.Printf("Valid! row:%d, col:%d, val:%d \n", freeRow, freeCol, i)
            board.BoardArray[freeRow][freeCol] = i
            if board.SolveBoard(i, freeRow, freeCol + 1) {
                return true
            }
            board.BoardArray[freeRow][freeCol] = 0
        }
    }
    return false
}

// Checks all valid pos funcs
// Returns true if valid
func (board *Board) ValidPos(cellVal int, row int, col int) bool {
    isValidInRow := board.ValidPosInRow(cellVal, row)
    isValidInCol := board.ValidPosInCol(cellVal, col)
    isValidInSubGrid := board.ValidPosInSubGrid(cellVal, col, row)

    if isValidInRow && isValidInCol && isValidInSubGrid {
        return true
    }
    return false
}

// Checks next free pos on board
// Returns [row,col] of free pos
func (board *Board) FreePos() []int {
    for row := 0; row < len(board.BoardArray); row++ {
        for col := 0; col < len(board.BoardArray[row]); col++ {
            if board.BoardArray[row][col] == 0 {
                validPos := []int{row, col}
                return validPos
            }
        }
    }
    return nil
}

// Check if cellVal can be placed in the row rowN
// Returns true if valid pos
func (board *Board) ValidPosInRow(cellVal int, row int) bool {
    for i := 0; i < len(board.BoardArray[row]); i++ {
        currentValue := board.BoardArray[row][i]
        if currentValue == cellVal {
            return false
        }
    }
    return true
}

// Check if cellVal can be placed in the column colN
// Returns true if valid pos
func (board *Board) ValidPosInCol(cellVal int, col int) bool {
    for i := 0; i < len(board.BoardArray[col]); i++ {
        currentValue := board.BoardArray[i][col]
        if currentValue == cellVal {
            return false
        }
    }
    return true
}

// Check if cellVal can be placed in the 3x3 subgrid
// Returns true if valid pos
func (board *Board) ValidPosInSubGrid(cellVal int, colN int, rowN int) bool {
    rowStart := (rowN / 3) * 3
    colStart := (colN / 3) * 3

    // Go through the subgrid,
    // check if any values are equal to cellVal
    for row := rowStart; row < rowStart+3; row++ {
        for col := colStart; col < colStart+3; col++ {
            subGridCell := board.BoardArray[row][col]
            if subGridCell == cellVal {
                return false
            }
        }
    }
    return true

}

// Helper Method:
// Prints board nicely
func (board *Board) PrintBoard() {
  for row := 0; row < len(board.BoardArray); row++{
    fmt.Printf("row:%d \t", row)
    for col := 0; col < len(board.BoardArray[row]); col++{
      fmt.Printf("%d|", board.BoardArray[row][col])
    }
    fmt.Println()
  }
}

main.go

package main

import (
    "board"
)

func main() {
    grid := [9][9]int{}

  board := board.Board{BoardArray: grid}
  board.GenerateBoard(0, 0)

  board.PrintBoard()

}
bobdylan01
  • 158
  • 2
  • 10
  • Please provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) (something we can run). While the loop may appear to do the same thing eight times remember that with each call to `GenerateBoard` a new random number is generated so there is a chance of a solution (also a chance you will miss valid solutions). Note that `rand.Seed` should generally only be [called once](https://stackoverflow.com/a/12321192/11810946). – Brits Feb 24 '22 at 19:54
  • @Brits updated code – bobdylan01 Feb 24 '22 at 21:01
  • does my comment answer your question (each iteration of the loop tries a different solution - each of these may succeed or fail). Here is your [algorithm in the playground](https://go.dev/play/p/DOajZriZNmR) - it succeeds with 5 iterations (removed `rand.Seed` to make this repeatable). – Brits Feb 24 '22 at 21:23
  • @Brits I don't think this is the case (?) In each iteration up to 8 the same number is tried. [playground](https://go.dev/play/p/vwqCoqIJryc) – bobdylan01 Feb 24 '22 at 22:13

1 Answers1

1

Lets output each attempt made (using this app - note that to save space I have put multiple attempts on each line):

Board is valid? true         Board is valid? true        Board is valid? true      
row:0   6|0|0|0|0|0|0|0|0|   row:0  6|7|0|0|0|0|0|0|0|   row:0  6|7|3|0|0|0|0|0|0|
row:1   0|0|0|0|0|0|0|0|0|   row:1  0|0|0|0|0|0|0|0|0|   row:1  0|0|0|0|0|0|0|0|0|
row:2   0|0|0|0|0|0|0|0|0|   row:2  0|0|0|0|0|0|0|0|0|   row:2  0|0|0|0|0|0|0|0|0|
row:3   0|0|0|0|0|0|0|0|0|   row:3  0|0|0|0|0|0|0|0|0|   row:3  0|0|0|0|0|0|0|0|0|
row:4   0|0|0|0|0|0|0|0|0|   row:4  0|0|0|0|0|0|0|0|0|   row:4  0|0|0|0|0|0|0|0|0|
row:5   0|0|0|0|0|0|0|0|0|   row:5  0|0|0|0|0|0|0|0|0|   row:5  0|0|0|0|0|0|0|0|0|
row:6   0|0|0|0|0|0|0|0|0|   row:6  0|0|0|0|0|0|0|0|0|   row:6  0|0|0|0|0|0|0|0|0|
row:7   0|0|0|0|0|0|0|0|0|   row:7  0|0|0|0|0|0|0|0|0|   row:7  0|0|0|0|0|0|0|0|0|
row:8   0|0|0|0|0|0|0|0|0|   row:8  0|0|0|0|0|0|0|0|0|   row:8  0|0|0|0|0|0|0|0|0|

Board is valid? false        Board is valid? true        Board is valid? false      
row:0   6|7|3|3|0|0|0|0|0|   row:0  6|7|3|5|0|0|0|0|0|   row:0  6|7|3|5|7|0|0|0|0|
row:1   0|0|0|0|0|0|0|0|0|   row:1  0|0|0|0|0|0|0|0|0|   row:1  0|0|0|0|0|0|0|0|0|
row:2   0|0|0|0|0|0|0|0|0|   row:2  0|0|0|0|0|0|0|0|0|   row:2  0|0|0|0|0|0|0|0|0|
row:3   0|0|0|0|0|0|0|0|0|   row:3  0|0|0|0|0|0|0|0|0|   row:3  0|0|0|0|0|0|0|0|0|
row:4   0|0|0|0|0|0|0|0|0|   row:4  0|0|0|0|0|0|0|0|0|   row:4  0|0|0|0|0|0|0|0|0|
row:5   0|0|0|0|0|0|0|0|0|   row:5  0|0|0|0|0|0|0|0|0|   row:5  0|0|0|0|0|0|0|0|0|
row:6   0|0|0|0|0|0|0|0|0|   row:6  0|0|0|0|0|0|0|0|0|   row:6  0|0|0|0|0|0|0|0|0|
row:7   0|0|0|0|0|0|0|0|0|   row:7  0|0|0|0|0|0|0|0|0|   row:7  0|0|0|0|0|0|0|0|0|
row:8   0|0|0|0|0|0|0|0|0|   row:8  0|0|0|0|0|0|0|0|0|   row:8  0|0|0|0|0|0|0|0|0|

As you will note with each attempt a different number (pseudo random) is being tried. If the board is valid then the algorithm attempts to add another number into the next free slot. The important bit to note is that where the board is invalid (6|7|3|3) an attempt is made with a different value (6|7|3|5) - without the loop that second attempt would not happen.

I think that a simple change to your code will make it easier to see why the loop makes a difference (the is effectively the same as your code - it just removes some unnecessary work):

randNum := rand.Intn(10-1) + 1
if board.ValidPos(randNum, freeRow, freeCol) {
    board.BoardArray[freeRow][freeCol] = randNum
    for i := 0; i <= 8; i++ {
        if board.GenerateBoard() {
            return true
        }
    }
    board.BoardArray[freeRow][freeCol] = 0
}
return false

The loop is important because it controls how often board.GenerateBoard() is called recursively. Each time a recursive call is made the code attempts to add another pseudo random number into the next available slot and the more attempts that are made the more likely it is that a solution will be found.

Brits
  • 14,829
  • 2
  • 18
  • 31