How does the following recursive Sudoku solving function work? - python

I recently watched this video showing a recursive function to solve sudoku and this seems irrational, since at the end we always change the value back to zero in the end.
How come the function worked in the video but doesnt work for me? Should the grid remain the same for both of us since at the end we use grid[y][x] = 0 which resets the grid discarding all changes made?
The idea of this code is to iterate over each number cell and each number (1-9) and check if it is possible to put the number in that cell. if we reach a dead end we backtrack.
Here's the code I manually copied:
import numpy as np
global grid
grid = [[5,3,0,0,7,0,0,0,0],
[6,0,0,1,9,5,0,0,0],
[0,9,8,0,0,0,0,6,0],
[8,0,0,0,6,0,0,0,3],
[4,0,0,8,0,3,0,0,1],
[7,0,0,0,2,0,0,0,6],
[0,6,0,0,0,0,2,8,0],
[0,0,0,4,1,9,0,0,5],
[0,0,0,0,8,0,0,7,9]]
def possible(y,x,n):
global grid
for i in range(0, 9):
if grid[y][i] == n:
return False
for i in range(0, 9):
if grid[i][x] == n:
return False
x0 = (x//3)*3
y0 = (y//3)*3
for i in range(0, 3):
for j in range(0, 3):
if grid[y0+i][x0+j] == n:
return False
return True
def solve():
global grid
for y in range(9):
for x in range(9):
if grid[y][x] == 0:
for n in range(1, 10):
if possible(y,x,n):
grid[y][x] = n
solve()
grid[y][x] = 0
return
print(np.matrix(grid))
print("")
solve()
print(np.matrix(grid))

The problem is that the solve function does indeed reset the grid back to its initial state after has finished executing, but it also solves the sudoku.
In the video, note that the grid is printed inside the solve function, not after it:
def solve():
global grid
for y in range(9):
for x in range(9):
if grid[y][x] == 0:
for n in range(1, 10):
if possible(y,x,n):
grid[y][x] = n
solve()
grid[y][x] = 0
return
print(np.matrix(grid))
print(np.matrix(grid))
print("")
solve()
This works because it loops through every cell and only recurses if the cell does not yet have a value filled in, and then after it has tried every value, it returns.
The only way it will complete the for y in range(9): loop is if it never finds a cell that is not 0, i.e. if the sudoku is solved, so by printing the matrix after the loop completes it will ensure that it prints the solved sudoku.

Your algorithm is working fine but you don't do anything with the grid when you find a solution. Once a solution is discovered, the backtracking zeroes out all of the cells it's filled in as it unwinds the call stack. By the time you get back to the main scope and print the result, it's back to its original state.
Your algorithm ends when you've iterated all of the rows and columns in a call to solve and find that there are no zeroes left to fill in. After your loops end, print (or better yet, return) the result and you'll see the completed puzzle.
Also, better to pass grid as a parameter to solve rather than use the global keyword. Sharing state like this makes it far too easy to introduce bugs.

Related

Trouble understanding this sudoku solver recursion function (python)

So basically I decided to make a sudoku solver in python and recursive functions is most efficient, but I've been trying to understand this code from a youtuber and don't understand why each space (bo[row][col]) doesn't immediately reset to 0. The resetting of one space to 0 only occurs if solve(bo) is True, but if I look through the code, the board will only return True if the board is completely solved, so why doesn't this function just lead nowhere since solve(bo) will never be True?
def solve(bo):
find = find_empty(bo)
if not find:
return True # This is the only time that solve(bo) == True
else:
row, col = find
for i in range(1, 10):
if valid(bo, i, (row, col)):
bo[row][col] = i
if solve(bo):
return True
bo[row][col] = 0 # Yet here it resets the space to 0 if solve(bo) is False
return False
def valid(bo, num, pos):
for i in range(9):
if bo[pos[0]][i] == num and pos[1] != i:
return False
for i in range(9):
if bo[i][pos[1]] == num and pos[0] != i:
return False
box_x = (pos[1] // 3) * 3
box_y = (pos[0] // 3) * 3
for i in range(box_y, box_y + 3):
for j in range(box_x, box_x + 3):
if bo[i][j] == num and (i, j) != pos:
return False
return True
def find_empty(bo):
for y in range(9):
for x in range(9):
if bo[y][x] == 0:
return (y, x)
return False
There is some discrepancy between your explanation and comments in the code, but I'll do my best to explain the code anyway.
First solve() will try to find the first empty spot (i.e. one that is 0). If it can't find any, then the board is solved so it returns true.
If it does find a spot, then it will try to place numbers 1-9 there and check if it works with the previously entered numbers. Say 7 works (we don't know if 8 or 9 work as well because we haven't checked that yet). So we set the empty space to 7 and then pass this updated board in a recursive call. Essentially, this is like saying "if I force this spot to have the number 7, can you find a solution?"
If the recursive call returns true, it means there is a solution with 7 in that spot and hence the board has a solution, so we return true. If the recursive call to solve() returns false, then we know that there is no solution to the board with 7 in this spot, so we reset this spot to 0 and then try 8 (and then 9 if needed).
The thing to remember is that there is only one board (bo) in all the recursive calls - in other words, all the function calls are operating on the same variable bo. It doesn't create a copy of the board every time you make a recursive call. Lookup 'Pass by reference' and shallow vs. deep copies if you want to learn more about why.

Fill out sudoku board - backtracking solution question

I wrote the following solution for the Leetcode question copied below:
Write a program to solve a Sudoku puzzle by filling the empty cells.
A sudoku solution must satisfy all of the following rules:
Each of the digits 1-9 must occur exactly once in each row. Each of
the digits 1-9 must occur exactly once in each column. Each of the the
digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of
the grid. Empty cells are indicated by the character '.'.
Note:
The given board contain only digits 1-9 and the character '.'. You may
assume that the given Sudoku puzzle will have a single unique
solution. The given board size is always 9x9.
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
EMPTY_CELL = '.'
target = set(str(n) for n in range(1, 10))
def _validate():
cols = [[board[r][c] for r in range(9)] for c in range(9)]
boxes = []
for i in (0, 3, 6):
for j in (0, 3, 6):
boxes.append([board[a][b] for a in range(i, i + 3) for b in range(j, j + 3)])
valid_rows = all(set(row) == target for row in board)
valid_cols = valid_rows and all(set(col) == target for col in cols)
valid_boxes = valid_cols and all(set(box) == target for box in boxes)
return valid_boxes
def helper(r, c):
if c == len(board[0]):
return helper(r + 1, 0)
if r == len(board):
return _validate()
if not board[r][c] == EMPTY_CELL:
return helper(r, c + 1)
for n in range(1, 10):
board[r][c] = str(n)
if helper(r, c + 1):
return True
return False
helper(0, 0)
Here's my strategy in plain English. For every cell that is empty, I try placing a number in that cell, and recursing on the remainder of the board. If that doesn't lead to a valid solution, I backtrack, increment the number, and recurse having placed that number in the empty cell instead.
My validate function is returning False for everything, and I'm ending up with a board with 9's in the empty spaces. The problem guarantees that there IS a correct solution for every test case. I've walked through this code dozens of times and am unable to see what the issue is.
(I understand that I could use constraint propagation to speed up the solution, but the current problem isn't that my solution is too slow - it is that its incorrect).
Anyone see why? Also, in case this is unclear from the problem statement, each digit is supposed to be a string.
Your validate function will return true if you feed it a correct solution. You can verify this yourself by feeding it a solved sudoku board:
solved_text = '''435269781
682571493
197834562
826195347
374682915
951743628
519326874
248957136
763418259'''
solved_board = [ list(line) for line in solved_text.split('\n') ]
There are two problems. First, you do not actually search the complete space of solutions. If you print each complete board passed into _validate(), you will notice something odd: the whole board is always in lexical order! This is not the 10^81 set of possible boards. This can be fixed by simply omitting these two lines of code:
if not board[r][c] == EMPTY_CELL:
return helper(r, c + 1)
These are causing a problem because you mutate board state as a side affect as you go but do not clean-up (put back empty cells) while backtracking. You can simply omit those two lines (so that the algorithm in helper() never cares about what is to the right in the (r,c) lexical ordering) or by adding code to set board[r][c] = EMPTY_CELL when back-tracking.
The other problem is that because you only run validation on complete boards, and because your validation function can only check complete boards for correctness, your algorithm really will have to plow through all 10^81 possible boards before it finds a solution. This isn't just slow, it's completely intractable! Instead, you should rewrite your validation function so that it can validate a partial board. For example, if the first row is ['1', '1', '.', ..., '.'], it should return False, but if the first row is ['1', '2', '.', ..., '.'] it should return True because there are no problems so far. Obviously, you will also have to call _validate() at every step now, not just when the board is complete... but this is worth it because otherwise you will spend enormous amounts of time exploring boards which are obviously never going to work.
You will need to fix both problems before your algorithm will work.
You are not having a right validation! Your validation only works for the final solution. Unless you are trying to general all possible fill-outs for your sudoku, this validation do not give you any check (and always false).
The pseudo-code of what a backtracking algorithm in my mind is the following:
Scan from cell (0,0) up to cell (8,8), find an empty one
Test out options "1" to "9"
call validation on each option, if valid, recur to the scan line above
if failed validation, try other option
if exhaused all options "1" to "9", previous level of recursion is invalid, try another one
So the validation is not to check if all rows, columns, boxes have 1 to 9, but to check if they have no duplicate! In python code, it means len(x) == len(set(x)) for x the row, column, or box, which takes only "1" to "9" but not ".".

Recursive function for connect four possible moves generation in Python

I am programming a connect four AI for my school project. But first I want to simulate every possible moves on the 6(row)*7(column) before I write the minimax algorithm to work the perfect move for each stage of board.
Instead of using
for a in range(7):
place_player_disk()
complete_set.append
if check_win():
continue
for b in legal_move():
place_AI_disk()
complete_set.append
if check_win()
continue
.... #repeat the nested for loop 42 times
I want to use a neater way of doing it
state0=[['','','','','','',''],['','','','','','',''],['','','','','','',''],['','','','','','',''],['','','','','','',''],['','','','','','',''],['','','','','','','']]
complete_set=[[],[],[],[],[],[]...]
import copy
def playerplacetoken(perimeter,location):
count=0
for row in range(6):
if perimeter[row][location]=='X' or perimeter[row][location]=='Y':
count+=1
perimeter[5-count][location]='X'
def full(perimeter):
free = []
for column in range(7):
if perimeter[0][column] == '':
free.append(column)
return free
def PlacingCPUToken(perimeter,location):
count=0
for row in range (6):
if perimeter[row][location]=='X' or perimeter[row][location]=='Y':
count+=1
perimeter[5-count][location]='Y'
def CheckP(perimeter):
changerow=[0,1,1]
changecolumn=[1,0,1]
Pwin=False
for col in range(7):
for row in range (6):
for change in range (3):
try:
consecutivecount=0
for nochange in range(4):
if perimeter[row+(changerow[change]*(nochange))] [col+(changecolumn[change]*(nochange))]=='X':
consecutivecount+=1
if consecutivecount==4:
Pwin=True
except:
continue
return Pwin
def CheckC(perimeter):
changerow=[0,1,1]
changecolumn=[1,0,1]
Cwin=False
for col in range(7):
for row in range (6):
for change in range (3):
try:
consecutivecount=0
for nochange in range(4):
if perimeter[row+(changerow[change]*(nochange))][col+(changecolumn[change]*(nochange))]=='Y':
consecutivecount+=1
if consecutivecount==4:
Cwin=True
except:
continue
return Cwin
def recursive(state,move): #state: the state of board, first starts with an empty board and as the function loops, the state changes Move: no of moves taken by both the player and the computer
for a in full(state): #full returns a list of legal moves, which means columns that are not full
state1= copy.deepcopy(state)
playerplacetoken(state1, a)
complete_set[move].append(state1)
if CheckP(state1): #Check p returns boolean, checking if the player has won
continue
for b in full(state1):
state2= copy.deepcopy(state1)
PlacingCPUToken(state2, b)
complete_set[move+1].append(state2)
if CheckC(state2): #Check C checks if the computer wins and return a boolean
continue
while move<44:
move+=2
recursive(state2,move)
recursive(state0,0)
but this doesn't work properly (I mean it has no error but the results are not correct)
I dont really know how to use a recursive function.Please help!
You do a deep copy of the state to compute further moves.
You create a new complete_set for each recursive invocation.
You never return anything from the recursive step.
Because of this, there's no way for information computed in the nested steps to trickle up to the calling step.
UPDATE: the state is indeed updated by a recursive step: complete_set[...].append(...) does that, because complete_set is global.
Try to think about the type of the function you're trying to write.
E.g. it takes a state and an integer depth level. It returns a list of possible moves, which is empty if the depth it too big.
What you may actually want is a tree of moves, where each element of your list is a pair: (move, [...]). The list contains pairs of the same type: a move with a subtree of moves possible after it, etc. The leaves have an empty set of possible moves.
You might consider calculating the immediate utility function value right when you're building the nodes.
If you make the calculation lazy (using yield and generators), alpha-beta pruning will become easy to implement, too.

Recursion function cooperating with another function python

I am trying to make a game solver. It should be able to solve Sudoku i.e.
find each possible number useable in each square.
This is what I have:
def next_possible_moves(board,row,col):
possible_moves = set(range(1,len(board)+1))
rows = set(board[row])
cols = []
for j in range(0,len(board)):
cols.append(board[j][col])
cols = set(cols)
possible_moves = possible_moves - rows.union(cols)
return list(possible_moves)
def solve(board):
for i in range(0,len(board)):
if len(board) == 8 and len(board[i]):
print "Found a solution:", board
return True
for next_move in next_possible_moves(board):
if is_valid(board, next_move):
if solve(board + [next_move]):
return True
return False
Both are working, but I am not able to get them to work together. I am using recursion to make it use one number, and while going through the code, it will backtrack if it fails.
What I want is for next_possible_moves to use only one parameter, so I can remove "row" and "col".
Using a list with X lists inside, so it represent an "X times X" board.

Why two while loops one after the other (not inside the other) in Python don't work?

I wrote the code below and I was expecting that, when the first loop ends and doesn't return False, the flow would follow to the second while loop. However, the flow skips the second while loop and simply returns True. Why is that? How can I fix this problem, making the flow after the first while loop go to the second while loop?
square = [[1,2,3,4],[4,3,1,4],[3,1,2,4],[2,4,4,3]]
# this is an auxiliary function
def getSum(lis):
sum = 0
for e in lis:
sum = sum + e
return sum
# here is where the problem is
def check_game(square):
standardSum = getSum(range(1, len(square)+1))
while square: #this is the first while loop
row = square.pop()
print row, 'row', 'sum of row=', getSum(row)
if standardSum != getSum(row):
return False
m = 0
while m < len(square): # the second while loop, which the flow skips
n = 0
col = []
while n < len(square):
col.append(square[n][m])
n = n + 1
print col, 'column'
if standardSum != getSum(col):
print standardSum, ' and sum of col =', getSum(col)
return False
m = m + 1
return True
The first loop only terminates when there are no more items left in square. After the first loop, len(square) will be 0, so the entry condition for the second loop m < len(square) will be False.
FYI your code is very (very very very) un-idiomatic Python -- it's written much more like C.
Here's a rewrite which is much more like Python is normally written.
square = [[1,2,3,4],[4,3,1,4],[3,1,2,4],[2,4,4,3]]
transpose = lambda i: zip(*i)
def is_magic(square):
n = len(square)
s = n*(n+1)/2
return all(sum(row) == s for row in square) and \
all(sum(col) == s for col in transpose(square))
You may wish to look into numpy, which is a Python module for handling matrices. With it:
def is_magic(square):
n = len(square)
s = n*(n+1)/2
return all(m.sum(0) == s) and all(m.sum(1) == s)
while square: will terminate when square is empty; it follows that len(square) == 0, and thus m < len(square) evaluates to false when m=0.
square.pop() returns a row from square and removes the row, therefore len(square) is zero in the second loop.
There is also a built-in function sum that does the same thing as your getSum function.
You know how many times you plan to iterate because you check a length and an increment variable. Use a for loop instead, as it will allow you to initialize the increment and adjust it each loop on the same line. This will avoid issues resulting in infinite loops in the future (even though thisn't the problem here, I consider it relevant to point out).
You can avoid your error by replacing your first while with this:
for row in square:
print row, 'row', 'sum of row=', getSum(row)
if standardSum != getSum(row):
return False

Categories