Python - Sudoku generator with Backtracking method - Results in endless loop - python

I'm writing a code for Sudoku, but facing endless loops here. The three conditions never seem to meet after a few cells have been occupied, even though I've used the Backtracking method.
I have used
def
for checking row, column and individual grids which works alright; I've checked them individually.
I've started placing numbers from the beginning with random numbers, and if and when they don't satisfy the checks, they enter a
while loop
with three conditions until the conditions are satisfied.
With the Backtracking method, I'm keeping track of the values that cannot go with a specific spot and eliminating them when the loop backtracks.
Here's the code:
import random
a = [['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-'],\
['-','-','-', '-','-','-', '-','-','-']]
def sudoku_board():
board = ''
for i in range(9):
if i==3 or i==6: board+='\n\n'
else: board += '\n'
for j in range(9):
if j==2 or j==5: board += str(a[i][j]) + '\t'
else: board+= str(a[i][j]) + ' '
return board
def col_check(x,y,num):
col = 0
for m in range(9):
if m!=x and a[m][y] == str(num):
col = 1
break
return col
def row_check(x,y,num):
row = 0
for m in range(9):
if m!=y and a[x][m] == str(num):
row = 1
break
return row
def grid_check(x,y,num):
grid = 0
for m in [(x//3),(x//3)+1,(x//3)+2]:
for n in [(y//3),(y//3)+1,(y//3)+2]:
if a[m][n] == str(num):
grid = 1
break
return grid
row,col = -1,-1
assign = {}
while row<8 and row>=-1:
row+=1
col=-1
while col<8 and col>=-1:
col+=1
rand_num = random.randint(1,9)
num = [1,2,3,4,5,6,7,8,9]
nump = [1,2,3,4,5,6,7,8,9]
counter = 0
print('row',row,'col',col)
while row_check(row,col,rand_num) or grid_check(row,col,rand_num):
counter +=1
print('counter',counter)
if counter<9 and assign.get((row,col),False) == False:
num.remove(rand_num)
rand_num = random.choice(num)
elif counter<9 and assign.get((row,col),False) != False:
for k in assign.get((row,col)):
if k in nump:
nump.remove(k)
rand_num = random.choice(nump)
else:
counter = 0
a[row][col] = '-'
if col>0:
col-=1
elif col==0 and row>0:
row-=1
col = 8
elif row==0 and col==0:
continue
num = [1,2,3,4,5,6,7,8,9]
nump = [1,2,3,4,5,6,7,8,9]
a[row][col] = str(rand_num)
assign[(row,col)] = []
assign[(row,col)].append(rand_num)
print(sudoku_board())
I did some debugging in GDB, and I find the variables are never progressing after reaching row:1 and col:8 while they are moving from row:1 and col:5 back and forth endlessly.
Can someone tell me what the problem is? I'm struggling with this for over a month.
P.S- It's my first time posting, so apologies in advance if something's wrong. Thanks in advance!

Related

How can i optimize my sudoku generator code to generate sudoku puzzles faster

I modified the sudoku generator code I found at https://medium.com/codex/building-a-sudoku-solver-and-generator-in-python-1-3-f29d3ede6b23 to look like this:
def generateBoard():
print("Generating puzzle...please wait")
time.sleep(1)
gameboard = createEmpty() # creates a list with the three diagonal boxes filled and the rest filled with zeros
gameboard = solve(gameboard) # changes the unsolved board DIRECTLY to a solved board
gameboard = removeNumbers(gameboard,38)
print("\nHere's your puzzle :)")
printBoard(gameboard)
So what the above code does is, it first creates a list in which the three 3x3 subgrids in the leading diagonal are filled with numbers 1 through 9 (sudoku rules) using the createEmpty() function below:
def createEmpty():
board = [[0 for i in range(9)] for j in range(9)]
for i in range(3):
box = shuffle(range(1,10))
for y in range(i*3, i*3+3):
for x in range(i*3, i*3+3):
board[y][x] = box.pop()
return board
This function was only returning a 9x9 list filled with zeros (first line) before, but according to the article i read in that website, it said that using this method of filling the three boxes in the diagonal with numbers before solving would save some time in backtracking so i changed it and it affected the time spent but by only a little amount.
def solve(board):
empty = find_empty(board)
if not empty:
return True
col, row = empty
for num in shuffle(range(1,10)):
if valid(board, num, empty):
board[row][col] = num
if solve(board):
return board
board[row][col] = 0
return False
So after creating the list, it uses the solve() function above👆👆 which uses backtracking to solve the partially filled board.
So after getting a solved sudoku board, i created a function (removeNumbers) to remove numbers from the board to create the puzzle. The function and its dependent functions are below:
def removeNumbers(solvedboard,hints=None):
board = copy.deepcopy(solvedboard)
remaining = 81
if not hints: hints = random.randrange(28, 40)
for y in range(3):
for x in range(3):
boxCoords = [(i,j) for j in range(y*3, (y*3)+3) for i in range(x*3, (x*3)+3)]
for n in range(random.choice(range(1,5))):
a, b = random.choice(boxCoords)
board[b][a] = 0
boxCoords.remove((a,b))
remaining -= 1
coords = [(i,j) for i in range(9) for j in range(9)]
coords = list(filter(lambda p: board[p[0]][p[1]] != 0, coords))
random.shuffle(coords)
for row, col in coords:
if remaining == hints:
break
num = board[row][col]
board[row][col] = 0
n = numberOfSolutions(board, solvedboard)
if n != 1:
board[row][col] = num
continue
remaining -= 1
#printBoard(board)
#print(remaining, hints)
return board
def numberOfSolutions(board, solved=[], ret=False):
solutions = []
# find a solution in which an empty square has a valid value and add it to the list(solutions)
for row in range(9):
for col in range(9):
if board[row][col] == 0:
dupeBoard = copy.deepcopy(board)
solution = getSolution(dupeBoard, row, col)
if not ret and solution != solved:
return 0
if solution:
solutions.append(code(solution))
solutions = list(set(solutions))
return len(solutions) if not ret else solutions
def getSolution(board, row, col):
for n in range(1, 10):
# insert a valid number into the (row,col) and return a solved board or if there are no solutions for that board, return False
if valid(board, n, (col, row)):
board[row][col] = n
if (solvedboard:=solve(board)):
return solvedboard
board[row][col] = 0
return False
def valid(board, num, pos):
x, y = pos
# check rows
if board[y].count(num) > 0:
return False
# check columns
if [board[j][x] for j in range(9)].count(num) > 0:
return False
# 3x3 grid
grid_x, grid_y = x-(x % 3), y-(y % 3)
grid = [board[j][i] for j in range(grid_y, grid_y+3) for i in range(grid_x, grid_x+3)]
if grid.count(num) > 0:
return False
return True
The removeNumbers() function has a local variable hints which represents the number of clues present in the sudoku puzzle and which contains a random number from 28 to 40, with 28 being the hardest and 39 being the simplest.
After removing the numbers from the board, the resulting board is printed out using the printBoard() function below:
def printBoard(board):
print("-"*41)
for y in range(len(board)):
if y % 3 == 0:
print(('||'+'-'*11)*3, end='')
print('||')
print(f"|| {board[y][0] if board[y][0]!=0 else ' '} | {board[y][1] if board[y][1]!=0 else ' '} | {board[y][2] if board[y][2]!=0 else ' '} ||", end='')
print(f" {board[y][3] if board[y][3]!=0 else ' '} | {board[y][4] if board[y][4]!=0 else ' '} | {board[y][5] if board[y][5]!=0 else ' '} ||", end='')
print(f" {board[y][6] if board[y][6]!=0 else ' '} | {board[y][7] if board[y][7]!=0 else ' '} | {board[y][8] if board[y][8]!=0 else ' '} ||")
print(('||'+'-'*11)*3, end='')
print('||')
print('-'*41)
Right now my generator generates simple sudoku puzzles in 30+ seconds and hard sudoku puzzles in 3+ minutes. And the code in that website generates simple in 0.22 secs, medium in 2+ secs and hard in 20+ secs.
But after several debugging, I found out that a large amount of time is spent at the removeNumbers() function. Having said all of that, I'll appreciate it if you could help me to optimize the removeNumbers() function and therefore reduce the amount of time spent generating the sudoku puzzle.

How can I regroup my two for loops into one with only a tiny difference between the two?

I have the following code with the only difference being the j and the I position in my list. Is there any way to make it better by writing a function or something like that because I can't quite figure it out?
for i in range(dimension):
for j in range(dimension - 4):
k = 1
while k < 5 and gameboard[i][j+k] == gameboard[i][j]:
k += 1
if k == 5:
winner = gameboard[i][j]
for i in range(dimension):
for j in range(dimension - 4):
k = 1
while k < 5 and gameboard[j+k][i] == gameboard[j][i]:
k += 1
if k == 5:
winner = gameboard[j][i]
You could merge the two loops by inserting an additional nested loop that handles the permutations of i and j and the corresponding dimension deltas:
for i in range(dimension):
for j in range(dimension - 4):
for i,j,di,dj in [ (i,j,0,1), (j,i,1,0) ]:
k = 1
while k < 5 and gameboard[i+k*di][j+k*dj] == gameboard[i][j]:
k += 1
if k == 5:
winner = gameboard[i][j]
Generalized for all directions
Alternatively you could create a function that tells you if there is a win in a given direction and use that in a loop.
def directionWinner(board,i,j,di,dj):
player,pi,pj = board[i][j],i,j
# if player == empty: return
for _ in range(4):
pi,pj = pi+di, pj+dj
if pi not in range(len(board)): return
if pj not in range(len(board)): return
if board[pi][pj] != player: return
return player
Then use it to check all directions:
for pos in range(dimensions*dimensions):
i,j = divmod(pos,dimensions)
for direction in [ (0,1),(1,0),(1,1),(1,-1) ]:
winner = directionWinner(gameboard,i,j,*direction)
if winner is not None: break
else: continue; break
The directions are represented by the increase/decrease in vertical and horizontal coordinates (deltas) for each step of one. So [ (0,1),(1,0),(1,1),(1,-1) ] gives you "down", "across", "diagonal 1", diagonal 2" respectively.
Checking only from last move
The same idea can be used to check for a winner from a specific position (e.g. checking if last move is a win):
# count how may consecutive in a given direction (and its inverse)
def countDir(board,i,j,di,dj,inverted=False):
player,pi,pj = board[i][j],i,j
count = 0
while pi in range(len(board) and pj in range(len(board)):
if board[pi][pj] == player: count += 1
else: break
pi, pj = pi+di, pj+dj
if not inverted:
count += countDir(board,i,j,-di,-dj,True)-1
return count
def winnerAt(board,i,j):
for direction in [ (0,1),(1,0),(1,1),(1,-1) ]:
if countDir(board,i,j,*direction)>=5:
return board[i,j]
Then, after playing at position i,j, you can immediately know if the move won the game:
if winnerAt(gameboard,i,j) is not None:
print(gameboard[i][j],"wins !!!")
It's maybe a trivial change, but why don't you just put the second check in the first loop just by swapping indices and using another variable for the while part? I mean something like this:
def check_winner(gameboard, dimension):
for i in range(dimension):
for j in range(dimension - 4):
# perform the first check
k = 1
while k < 5 and gameboard[i][j+k] == gameboard[i][j]:
k += 1
if k == 5:
return gameboard[i][j]
# and the second check
l = 1
while l < 5 and gameboard[j + k][i] == gameboard[j][i]:
l += 1
if l == 5:
return gameboard[j][i]
Anyways, it's cleaner to just return if you found the winner, and avoid redundancy.

8 queen with hill climbing algorithm doesn't return anything?

I know this is kinda long but do you know why my 8 queen algorithm doesn't return anything. I created an empty board, similar neighbour board, and queens (for following where they are) I put one queen for each column and put them into random rows then I calculated total collision and then I put queens at other rows (at the same column) one by one and calculated total collision again. After that, I found the position that would create the minimal collision, and until I run out of positions that I can minimise the collision, and after breaking the first(calculates neighbours collision) loop I break the second loop(resets all queens positions) if collision is 0.
import random
from array import array
board = [[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]]
neighbour = [[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]]
queens = [0,0,0,0,0,0,0,0]
def collision_count(column,row):
coll = 0
for j in range(8):
if j == row:
continue
if board[column][j] == 1 :
coll += 1
while(column < 7 and row < 7):
row += 1
column +=1
if board[column][row] == 1:
coll += 1
while(column > 0 and row > 0):
row -= 1
column -=1
if board[column][row] == 1:
coll += 1
while(column > 0 and row < 7):
row += 1
column -=1
if board[column][row] == 1:
coll += 1
while(column < 7 and row > 0):
row -= 1
column +=1
if board[column][row] == 1:
coll += 1
return coll
def totalcoll():
totcoll = 0
for i in range(8):
totcoll += collision_count(i,queens[i])
return totcoll
while True:
for i in range(8):
queens[i] = random.randrange(0,8)
board[i][queens[i]] = 1
totalcollision = totalcoll()
while True:
for i in range(8):
oldqueen = queens[i]
for j in range(8):
queens[i] = j
neighbour[i][j] = totalcoll()
queens[i] = oldqueen
min = neighbour[0][0]
minqueencol = 0
minqueenrow = 0
for i in range(8):
for j in range(8):
if(neighbour[i][j]<min):
min = neighbour[i][j]
minqueenrow = j
minqueencol = i
if min<totalcollision:
totalcollision = min
queens[minqueencol] = minqueenrow
else:
break
if totalcollision == 0:
break
print("a")
for i in range(8):
for j in range(8):
print(board[i][j])
You've put infinite loop with while True. Your loops never break. So your program flow never reached to print(a) and print(board[i][j]) lines. I checked your code and your second while loop never breaks and works continuously. Please check your break conditions.
Actually I noticed a problem in your code: as far as I read the algorithm, if I understood correctly, you're miscalculating the number of collisions.
This picture is your board status.if I understood correctly the algorithm, there is 4 collision in there. (correct me if I'm wrong) But your totalcoll() function calculated it as 18. I ran the code again just to be sure. and I saw this: while there are 3 collisions, totalcoll() returns 13 collision.
There is also another sample:
This is your board with 5 collision. But this is what says your program about collision (23 collision):
You should check your totalcoll() func.

Save Result Sudoku Backtracking

I've implemented backtracking algorithm in python to solve a sudoku. During the steps of the algorithm I check, if the sudoku is already solved and when it is, I want to return it. But my function just returns None. Can someone explain me the reason for this and give me a hint for the solution?
#Test-Sudoku
grid = [[1,0,7,0,0,5,4,0,0],
[9,0,0,3,7,0,0,0,0],
[0,2,3,0,8,0,0,0,0],
[0,9,2,0,0,0,0,7,0],
[0,7,0,0,6,0,0,1,0],
[0,6,0,0,0,0,8,9,0],
[0,0,0,0,4,0,3,6,0],
[0,0,0,0,3,7,0,0,1],
[0,0,8,2,0,0,5,0,7]]
def possible(row,col,num,grid):
""" Check if num can be passed to grid[row][col]"""
if grid[row][col] != 0:
return False
for i in range(9):
if grid[row][i] == num or grid[i][col] == num:
return False
row0 = (row//3)*3
col0 = (col//3)*3
for i in range(3):
for j in range(3):
if grid[row0+i][col0+j] == num:
return False
return True
def is_solved(grid):
""" Check if Sudoku is already solved/full"""
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
return False
return True
def solve(grid):
""" Backtracking algorithm to solve Sudoku"""
for r in range(9):
for c in range(9):
if grid[r][c] == 0:
for i in range(1,10):
if possible(r, c, i, grid):
grid[r][c] = i
if is_solved(grid):
# Print grid for test
print("Show Solved Sudoku:")
print(grid)
return(grid)
solve(grid)
grid[r][c] = 0
return
return
solved_sudoku = solve(grid)
print(solved_sudoku)
because you use return which returns none
you store the result in grid and grid changes in every turn
try this:
def solve(grid):
""" Backtracking algorithm to solve Sudoku"""
for r in range(9):
for c in range(9):
if grid[r][c] == 0:
for i in range(1,10):
if possible(r, c, i, grid):
grid[r][c] = i
solve(grid)
if is_solved(grid):
return
grid[r][c] = 0
return
solve(grid)
print(grid)

How to implement recursion when searching for an unique sudoku (python)?

I am trying to write a function with recursion, but the return I get is empty each time... The code is as follows: first a random grid is generated (random_puzzle()). This is transformed from a string to an array, after which it is solved and checked if the created sudoku is unique (solve(grid)). What I want the code to do is keep making a new sudoku until it has found a puzzle that is unique. Why does my recursion not work?
Thanks in advance!
grids={}
Grids={}
final_grid = []
def solve(grid):
global uniq, count, proc_time
tic = time.perf_counter()
if uniq < 2:
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)
count += 1
grid[y][x] = 0
return grid
uniq += 1
toc = time.perf_counter()
proc_time = toc - tic
# print('Time needed to solve Sudoku: '+str(toc - tic)+' seconds')
# print('Steps needed:'+str(count))
return grid
def random_puzzle(N=25):
values = dict((s, digits) for s in squares)
for s in shuffled(squares):
if not assign(values, s, random.choice(values[s])):
break
ds = [values[s] for s in squares if len(values[s]) == 1]
if len(ds) >= N and len(set(ds)) >= 8:
return ''.join(values[s] if len(values[s])==1 else '.' for s in squares)
return random_puzzle(N)
def make_grid():
uniq=0
count=0
Grids=random_puzzle(N=25)
mat = Grids.replace('.','0')
grid = np.zeros([9,9])
countx = 0
county = 0
for j in mat:
if countx >= 8:
grid[county,countx] = j
countx = 0
county += 1
else:
grid[county,countx] = j
countx += 1
final_grid = solve(grid)
if uniq > 1:
make_grid()
elif uniq == 1:
return final_grid
test = make_grid()
print(test)

Categories