Python Sudoku Recursion with Brute Force Backtracking Error - python
I am doing a Sudoku puzzle solver for homework, but am encountering some difficulty. The code right now cycles past the solution, although it does reach it for easy puzzles, and for harder puzzles, it gets stuck with several 9's for no apparent reason. I would appreciate any help on this. (check_cell determines if the placement is valid or not.)
Is backtracking implemented correctly in this code, and if not, how would that be fixed?
How can I stop the solver from freezing? It solves around 3 rows and then freezes, changing most of the values to 9s.
Some code:
def solve_helper(self, row, col):
# Try placing a number in each column of current row
board = self.the_board
if board[row][col] != 0:
?????
elif board[row][col] == 0:
for i in range(1,10):
print("Setting value at i with ") + str (i) + (" located at " ) + str(row) + str(col)
self.set_cell(row, col, i)
self.guesses = self.guesses + 1
if self.check_cell(row, col):
if self.solve_helper(row, col): return True
else:
self.set_cell(row, col, 0)
else:
return self.mover(row,col)
return False
def mover(self, row, col):
if col + 1 != 9:
return self.solve_helper(row, (col+1))
elif row + 1 != 9:
print "Moving to row" + str(row + 1)
return self.solve_helper((row+1),0)
else:
print "SOLUTION FOUND"
return True
The trouble you're having is that some of your recursive calls are not returning the results properly, so your solution, when it is found, gets forgotten about a few levels up the recursive stack. Here's the first fix that you need, adding return to the recursive calls made in mover:
def mover(self, row, col):
if col + 1 != 9:
return self.solve_helper(row, (col+1)) # added return
elif row + 1 != 9:
print "Moving to row" + str(row + 1)
return self.solve_helper((row+1),0) # here too
else:
print "SOLUTION FOUND"
return True
You also need something similar in the special case of your solve_helper function where you skip over pre-solved cells. The end of the function should be:
else:
return self.mover(row, col) # added return
return False
Edit:
Ok, I've found a few more issues in the code. Two of them are logic issues with the solver, and one is a display issue that doesn't cause any real problems other than looking strange during the solving.
The issues:
First, you're latest code has solve_helper calling itself, rather than calling mover. This makes it take an extra function call before moving (though I think it may not actually break the solver).
Second, if solve_helper sets a cell to 9, but then in backtracked to (after some later cells couldn't be solved), the 9 doesn't get reset to zero before backtracking further.
And lastly, the display issue. Setting cells to 0 doesn't stop their old value from being displayed. This looked a lot like the issue in #2, (with 9s being left behind after backtracking) but in fact it is only cosmetic.
The first issue is easy to fix. Just change the solve_helper call to a mover call instead. That's actually what you had in the original code you put in the question. Calling solve_helper directly doesn't actually get the wrong result (since solve_helper will skip the already filled out cell the second time), but it adds an unnecessary extra function call to each level of your recursion.
The second issue is a little more complicated, and this is where you are getting stuck on some boards. What you need to do is move the line that does self.set_cell(row, col, 0) out of the else block it is currently in. In fact, you can actually move it outside of the loop entirely, if you want (since it only really is necessary if you're backtracking after none of the values for the current cell worked). Here is what I think this is the best arrangement of the for loop (also moving the return False statement up):
for i in range(1,10):
print("Setting value ") + str (i) + (" at " ) + str(row) + ", " + str(col)
self.set_cell(row, col, i)
self.guesses = self.guesses + 1
if self.check_cell(row, col):
if self.mover(row, col):
return True
print "Backtracking"
self.set_cell(row, col, 0)
return False
Finally, fixing the display issue requires two changes. First, get rid of the conditional in set_cell. You want to update the display always. Next, in update_textfield, move the delete call outside of the if block so that it always happens (leave the insert under the if). This makes it so that setting a cell to zero will erase the previous value, but not make it display an actual 0 character (it will show nothing).
I think this should do it. Note that the algorithm you're using is still pretty slow. Solving a board I found on the internet in a quick Google search took 122482 guesses and more than 5 minutes, but it did finally work. Other boards (especially those that require 8s or 9s in the first few open spaces) may take even longer.
Related
How do I run a conditional statement "only once" and every time it changes?
I might be asking a simple question. I have a python program that runs every minute. But I would like a block of code to only run once the condition changes? My code looks like this: # def shortIndicator(): a = int(indicate_5min.value5) b = int(indicate_10min.value10) c = int(indicate_15min.value15) if a + b + c == 3: print("Trade posible!") else: print("Trade NOT posible!") # This lets the processor work more than it should. """run_once = 0 # This lets the processor work more than it should. while 1: if run_once == 0: shortIndicator() run_once = 1""" I've run it without using a function. But then I get an output every minute. I've tried to run it as a function, when I enable the commented code it sort of runs, but also the processing usage is more. If there perhaps a smarter way of doing this?
It's really not clear what you mean, but if you only want to print a notification when the result changes, add another variable to rembember the previous result. def shortIndicator(): return indicate_5min.value5 and indicate_10min.value10 and indicate_15min.value15 previous = None while True: indicator = shortIndicator() if previous is None or indicator != previous: if indicator: print("Trade possible!") else: print("Trade NOT possible!") previous = indicator # take a break so as not to query too often time.sleep(60) Initializing provious to None creates a third state which is only true the first time the while loop executes; by definition, the result cannot be identical to the previous result because there isn't really a previous result the first time. Perhaps also notice the boolean shorthand inside the function, which is simpler and more idiomatic than converting each value to an int and checking their sum. I'm guessing the time.sleep is what you were looking for to reduce the load of running this code repeatedly, though that part of the question remains really unclear. Finally, check the spelling of possible.
If I understand it correctly, you can save previous output to a file, then read it at the beginning of program and print output only if previous output was different.
Index Out Of Range When Artificially Limited
when I run this program, sometimes I receive an error.This error however is not possible as I am using an 8x8 grid and I limit the inputs so that they can only be numbers from 0-7, to obey the fact that list indexes start at 0. The user must input coordinates (1-8),(A-H) and the program will check to see if those coordinates are correct, by systematically going through the CompShips list and repeatedly comparing those coordinates to ones given by the user. If the cords match, then a message will appear and a "Z" will change to an "X" on those coordinates, indicating a HIT. If the guess does not match, a "Z" will change to an "M" on those coordinates indicating a MISS. CompShips=[[1,0],[1,1],[2,2],[2,3],[2,4],[3,0],[3,1],[3,2],[5,4],[5,5],[5,6],[5,7],[1,7],[2,7],[3,7],[4,7],[5,7]] FRow1=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow2=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow3=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow4=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow5=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow6=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow7=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow8=["Z","Z","Z","Z","Z","Z","Z","Z",] def PrintFireBoard(): print(Index) print(FRow1) print(FRow2) print(FRow3) print(FRow4) print(FRow5) print(FRow6) print(FRow7) print(FRow8) FireBoard=[FRow1,FRow2,FRow3,FRow4,FRow5,FRow6,FRow7,FRow8] while len(CompShips) !=0 or CompSuccess==17: FireRow=input("Please Choose The Row That You Wish To Fire Upon (1-8) ") FireIndex=input("Please Choose The Column That You Wish To Fire Upon (A-H) ") #As Lists start at 0 FireRow=int(FireRow)-1 if FireIndex==("A"): FireIndex=0 elif FireIndex==("B"): FireIndex=1 elif FireIndex==("C"): FireIndex=2 elif FireIndex==("D"): FireIndex=3 elif FireIndex==("E"): FireIndex=4 elif FireIndex==("F"): FireIndex=5 elif FireIndex==("G"): FireIndex=6 elif FireIndex==("H"): FireIndex=7 Guess=[FireRow,FireIndex] #Check To See If Correct UserSuccess=0 for i in CompShips: if Guess==i: CompShips.remove(Guess) UserSuccess=1 else: pass if UserSuccess==1: print("HIT") print(FireRow) print(FireIndex) FireBoard[[FireRow][FireIndex]]=("H") PrintFireBoard() else: print("MISS") print(FireRow) print(FireIndex) FireBoard[[FireRow][FireIndex]]=("M") PrintFireBoard() I receive the error: IndexError: string index out of range
Looks like these two lines FireBoard[[FireRow][FireIndex]]=("H") FireBoard[[FireRow][FireIndex]]=("M") should be FireBoard[FireRow][FireIndex]="H" FireBoard[FireRow][FireIndex]="M" Explanation: In your old code, FireBoard[[FireRow][FireIndex]]=("H") [FireRow][FireIndex] means, given a list [FireRow] (which contains just one element), get the FireIndex-th element. This is not what you're trying to do. For example [3][0] returns 3, and [3][1] gives IndexError. Take a look at How to define a two-dimensional array in Python Also note that ("H") is the same as the string "H". There is no need to add parentheses.
Here is a much cleaner code! CompShips=[[1,0],[1,1],[2,2],[2,3], [2,4],[3,0],[3,1],[3,2], [5,4],[5,5],[5,6],[5,7], [1,7],[2,7],[3,7],[4,7], [5,7]] FRow=[["Z"]*8]*8 #1 More Pythonic def PrintFireBoard(): #print(Index) for i in range(0,8): print(FRow[i]) FireBoard=FRow[:] #NOTE THIS ONE!!! mydict = {} for i,key in enumerate(["A","B","C","D","E","F","G","H"]): #2 More Pythonic mydict[key] = i while len(CompShips) !=0 or CompSuccess==17: FireRow=input("Please Choose The Row That You Wish To Fire Upon (1-8) ") FireIndex=input("Please Choose The Column That You Wish To Fire Upon (A-H) ") FireRow=int(FireRow)-1 FireIndex = mydict[FireIndex] Guess=[FireRow,FireIndex] print(Guess) UserSuccess=0 for i in CompShips: if Guess==i: CompShips.remove(Guess) UserSuccess=1 else: pass if UserSuccess==1: print("HIT") print(FireRow,FireIndex) FireBoard[FireRow][FireIndex]="H" #3 your problem here PrintFireBoard() else: print("MISS") print(FireRow,FireIndex) FireBoard[FireRow][FireIndex]="M" PrintFireBoard() 1) As explained in the comments that's just a more nicer way to create a list of lists!. Remember DRY principle! Do Not Repeat yourself! 2) Instead of having all that if else to convert the 'A' to 0. You can use a dictionary lookup instead! 3) Your problem seems to be here! correct this to FireBoard[FireRow][FireIndex]="H" PS: NOTE THIS ONE!!!: I'm not just making FireBoard as an alias to FRow! I'm copying it into a FireBoard as a new list! There's a subtle difference read about it here. I'm doing this incase you don't want your original FRow list to be modified!
The indentation in your question was off. I think that all the code from Guess=[FireRow,FireIndex] until the end should be preceded by 4 spaces. I've removed print(Index) since it was not defined. To access FireBoard use: FireBoard[FireRow][FireIndex] Instead of FireBoard[[FireRow][FireIndex]] This should be working CompShips=[[1,0],[1,1],[2,2],[2,3],[2,4],[3,0],[3,1],[3,2],[5,4], [5,5],[5,6],[5,7],[1,7],[2,7],[3,7],[4,7],[5,7]] FRow1=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow2=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow3=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow4=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow5=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow6=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow7=["Z","Z","Z","Z","Z","Z","Z","Z",] FRow8=["Z","Z","Z","Z","Z","Z","Z","Z",] def PrintFireBoard(): print(FRow1) print(FRow2) print(FRow3) print(FRow4) print(FRow5) print(FRow6) print(FRow7) print(FRow8) FireBoard=[FRow1,FRow2,FRow3,FRow4,FRow5,FRow6,FRow7,FRow8] while len(CompShips) !=0 or CompSuccess==17: FireRow=input("Please Choose The Row That You Wish To Fire Upon (1-8) ") FireIndex=input("Please Choose The Column That You Wish To Fire Upon (A-H) ") #As Lists start at 0 FireRow=int(FireRow)-1 if FireIndex==("A"): FireIndex=0 elif FireIndex==("B"): FireIndex=1 elif FireIndex==("C"): FireIndex=2 elif FireIndex==("D"): FireIndex=3 elif FireIndex==("E"): FireIndex=4 elif FireIndex==("F"): FireIndex=5 elif FireIndex==("G"): FireIndex=6 elif FireIndex==("H"): FireIndex=7 Guess=[FireRow,FireIndex] #Check To See If Correct UserSuccess=0 for i in CompShips: if Guess==i: CompShips.remove(Guess) UserSuccess=1 else: pass if UserSuccess==1: print("HIT") print(FireRow) print(FireIndex) FireBoard[FireRow][FireIndex]=("H") PrintFireBoard() else: print("MISS") print(FireRow) print(FireIndex) FireBoard[FireRow][FireIndex]=("M") PrintFireBoard()
Python Connect 4 Game- Print X on Board
I am trying to create a connect four game but I cannot figure out how to print the checkers on the board. I need it to print the X at the lowest available spot in the column that the user inputted. Each time I run the program, I get the same blank 6 by 7 board. Please help. thank you! #ConnectFour numRows=6 numCols=7 numPlayers=2 board=[] checkers=['X','O'] turn=0 win = False for row in range(numRows): tempList=[] for col in range(numCols): tempList.append('.') board.append(tempList) while not win: turn = (turn+1)%numPlayers userCol = input ("Player"+str(turn+1)+"please enter your col: ") while not userCol.isdigit() or not int(userCol) in range(numCols): userCol = input ("Player"+str(turn+1)+"please enter your col: ") for row in range(numRows): for col in range(numCols): print(board[row][col], end='') print() for repeat in range(numRows-2): for row in range(numRows,-1,-1): for col in range(numCols): if board[row][int(userCol)]==".": board[row+1][int(userCol)]="X" print break #print board for row in range(numRows): for col in range(numCols): print(board[row+1][userCol],end=' ') print() print ("Player", checkers[turn],"has won the game.")
Here's what I noticed: Lines 24-27: This will print the unmodified board after a player drops a checker; perhaps this should be after lines 29-35 instead? Line 30: Looks like an off-by-one error, you might want range(numRows - 1, -1, -1) or reversed(range(numRows)) instead. Line 29: Why is it necessary to repeat the empty-space check? Line 31: What does this do? It iterates over each column, sure, but why is that necessary? You already know what column was requested, so why go over each column again? Lines 32-33: Seems to read "if the space is empty, place a checker in the slot below it". I'm thinking you probably want board[row][int(userCol)] = 'X' instead Line 41: Looks like another off-by-one error, row+1 should be row. Also, should userCol just be col? Otherwise it'll only be printing one column instead of the entire final board. General remarks: Generally you can achieve more concise and readable python code by avoiding the for i in range(x): do_something(mylist[i]) anti-pattern and instead using python-style iteration: for item in mylist: do_something(item). I would re-write lines 29-35. But before you do so, ask yourself exactly what those lines are supposed to do. If I understand the task correctly, you should only need one for loop there. Ninja edit: Also, once that's working, don't forget to handle the case that the player entered a valid column number, yet that column is completely filled, in which case you probably want to ask the same player to choose a new column.
Recursion in Python: what am I doing wrong?
I'm writing my first program in python and it has to simulate the mixing of particles (two gases). I don't know what am I doing wrong with this function. I don't want the particles to leave certain area, that is the walls of container. I use VPython. def poruszanie(lista,pozycja,numCell): flaga = 0 pozycjaTmp = (pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0) for i in range( 0, len(lista) ): if pozycjaTmp==lista[i].pos: flaga=1 if flaga==1: return poruszanie(lista,(pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0),numCell) elif pozycjaTmp[0]==0 or pozycjaTmp[0]==numCell or pozycjaTmp[0]==-numCell or pozycjaTmp[1]==numCell or pozycjaTmp[1]==-numCell: return poruszanie(lista,(pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0),numCell) return pozycjaTmp poruszanie - name of function pozycja - position of the sphere 0,numCell,-numCell - the borders of container (0 is the wall in the middle that separate the gasses in the beggining) All of this is in the x,y plane and z is always 0 That's where I start to use this function: while 1: rate(20) for i in range(0,len(self.listBalls)): self.listBalls[i].pos=poruszanie(self.listBalls,self.listBalls[i].pos,self.numCell)
I think you're calling it exactly the same each time and not returning properly: if flaga==1: return poruszanie(lista,(pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0),numCell) else: if pozycjaTmp[0]==0 or pozycjaTmp[0]==numCell or pozycjaTmp[0]==-numCell: poruszanie(lista,(pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0),numCell) elif pozycjaTmp[1]==numCell or pozycjaTmp[1]==-numCell: poruszanie(lista,(pozycja[0]+choice([-1,0,1]),pozycja[1]+choice([-1,0,1]),0),numCell) From the first call you return, but from the next two, you don't. Also, the parameters you're passing look to be exactly the same. Edit Considering the edit and your new error (maximum depth reached). That error means that your function is recursing farther than python allows. I'm still not sure what you're doing but you need a base condition at which point no matter what the recursion will stop. If you're satisfying either branch of the if ... elif ... statement each time you call the function, your recursion will never stop. You need something that will always break, and preferably you should place it before the if .. elif ... block.
python list Index out of range error
I am working on a python tetris game that my proffessor assigned for the final project of a concepts of programming class. I have got just about everything he wanted to work on it at this point but I am having a slight problem with one part of it. Whenever I start moving pieces left and right I keep getting "index out of range error". This only happens when it is up against a piece. Here are the culprits that are giving me grief. def clearRight(block=None): global board, activeBlock, stackedBlocks isClear = True if(block == None): block = activeBlock if(block != None): for square in block['squares']: row = square[1] col = square[0]+1 if(col >= 0 and stackedBlocks[row][col] !=None): isClear=False return isClear def clearLeft(block=None): global board, activeBlock, stackedBlocks isClear = True if(block == None): block = activeBlock if(block != None): for square in block['squares']: row = square[1] col = square[0]-1 if(col >= 0 and stackedBlocks[row][col] !=None): isClear=False return isClear I am not looking to get anyone to fix it for me, I'm only looking for tips on how to fix it myself. Thanks in advance for any help that is given.
There a typo that would cause that problem in the first method. When you're checking each cell in the block shifted one right, you don't check if they are off the grid. if (col >= 0 and ...) probably should be if (col < num_cols and ...) I also agree with CrazyDrummer, make a generic clear function Spoilers ... def clear(x_offset, block=None): if not block: block = activeBlock if not block: return True for x,y in block: x += x_offset if not (0 <= x < num_cols) or stackedBlocks[x, y]: return False return True
Look at what's different when you're getting the exception. Try printing out program state information to help you zero in. There's only one place where you access an array with variable indexes, so you can narrow your search radius a bit. Separate suggestion: Make a generic clear that takes determines what direction you want to clear from by the parameters. I highly recommend the book debugging rules!, it will aid you in searching out and properly fixing problems. :D