Backtracking pathinding problem in Python - python

Recently, I've found out about backtracking and without much thinking started on the book from the guy who has shown some Sudoku backtracking tricks (https://www.youtube.com/watch?v=G_UYXzGuqvM&ab_channel=Computerphile. Unfortunately, I'm stuck with the first backtracking problem without the solution.
The problem is formulated accordingly:
Use backtracking to calculate the number of all paths from the bottom left to the top right corner in a
x * y-grid. This includes paths like https://imgur.com/3t3Np4M. Note that every point can only be visited once. Write a function np(x,y) that returns the number of paths in a x*y-grid. E.g. np(2,3) should return 38. Hint: Create a grid of booleans where you mark the positions already visited.
Whatever I change in this short block of code I'm landing nowhere near 38.
```
grid = [[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0]]
solution = 0
def number_of_paths(x, y):
global solution
global grid
for i in range(0, x):
for j in range(0, y):
if grid[i][j] == 0:
grid[i][j] = 1
number_of_paths(x, y)
grid[i][j] = 0
solution += 1
return
if __name__ == '__main__':
number_of_paths(2, 3)
print(grid)
print(solution)```
That's a sample solution with solution with Sudoku solver.
```
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]]
import numpy as np
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()
# backtracking - bad choice
grid[y][x] = 0
return
print(np,matrix(grid))
input("More?")```

A few suggestions:
You might want to use a set for a grid, adding a square as soon as it is visited, if it is not a member of the set yet.
The counter and the grid can be global but it would probably be easier for you to take them as arguments for the function at first. After the solution is clearer you can worry about those details.
You are going about the problem the wrong way. It would be good to have one function calculating the number of paths from the origin to the destination (by calling the function for the neighbors that have not been visited yet. Make sure you update the grid). On top of that you can have a function that calls the path function for every combination of origin and destination. A small tip: You do not have to calculate the same path in reverse direction! You can have a map of calculate sums of paths. If the opposite direction has been calculate, don't bother. Later, double the amount of paths by 2.
Good luck!

I will show you a solution on a coordinate system where (0,0) is the topleft and (maxY,maxX) is the bot right. Going right increases x and going down increases y.
1- If you are trying to solve the exact maze in the image, then your grid array shape is wrong. Notice that you are travelling between corners of the squares, there are 4 points you can be horizontally and 3 points you can be vertically.
2- Hint is telling you about using a boolean mask for visited state, you already have a grid array so a separate array is not necessary.
3- The main problem with your code is how you are progressing in the maze. The loop structure
for i in range(0, x):
for j in range(0, y):
does not make sense because when you are in a position (x, y), you can only move in 4 main directions (right, up, left, down). However this loops make it look like you are trying to branch into all positions behind you, which is not valid. In my code I will explicity show about this traverse stuff.
grid = [[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 0, 0, 0]]
# number of solutions
solution = 0
# maximum values of x and y coordinates
maxX = len(grid[0])-1
maxY = len(grid)-1
# endpoint coordinates, top(y=0) right(x=maxX) of the maze
endX = maxX
endY = 0
# starting point coordinates, bottom(y=maxY) left(x=0) of the maze
mazeStartX = 0
mazeStartY = maxY
def number_of_paths(startX, startY):
global solution
global grid
global mask
# if we reached the goal, return at this point
if (startX == endX and startY == endY):
solution += 1
return
# possible directions are
#RIGHT (+1x, 0y)
#UP (0x, -1y)
#LEFT (-1x, 0y)
#DOWN (0x, +1y)
# I use a direction array like this to avoid nested ifs inside the for loop
dx = [1, 0, -1, 0]
dy = [0, -1, 0, 1]
for d in range(len(dx)):
newX = startX + dx[d]
newY = startY + dy[d]
# out of maze bounds
if (newX < 0 or newY < 0):
continue
# out of maze bounds
if (newX > maxX or newY > maxY):
continue
if (grid[newY][newX] == 1):
# this are is already visited
continue
else:
# branch from this point
grid[newY][newX] = 1
number_of_paths(newX, newY)
grid[newY][newX] = 0
if __name__ == '__main__':
number_of_paths(mazeStartX, mazeStartY)
print(grid)
print(solution)

Related

I was trying to use matrixes without libraries but I can't set the values correctly

def create_matrix(xy):
matrix = []
matrix_y = []
x = xy[0]
y = xy[1]
for z in range(y):
matrix_y.append(0)
for n in range(x):
matrix.append(matrix_y)
return matrix
def set_matrix(matrix,xy,set):
x = xy[0]
y = xy[1]
matrix[x][y] = set
return matrix
index = [4,5]
index_2 = [3,4]
z = create_matrix(index)
z = set_matrix(z,index_2, 12)
print(z)
output:
[[0, 0, 0, 0, 12], [0, 0, 0, 0, 12], [0, 0, 0, 0, 12], [0, 0, 0, 0, 12]]
This code should change only the last array
In your for n in range(x): loop you are appending the same y matrix multiple times. Python under the hood does not copy that array, but uses a pointer. So you have a row of pointers to the same one column.
Move the matrix_y = [] stuff inside the n loop and you get unique y arrays.
Comment: python does not actually have a pointer concept but it does use them. It hides from you when it does a copy data and when it only copies a pointer to that data. That's kind of bad language design, and it tripped you up here. So now you now that pointers exist, and that most of the time when you "assign arrays" you will actually only set a pointer.
Another comment: if you are going to be doing anything serious with matrices, you should really look into numpy. That will be many factors faster if you do numerical computations.
you don't need first loop in create_matrix, hide them with comment:
#for z in range(y):
# matrix_y.append(0)
change second one like this, it means an array filled with and length = y:
for n in range(x):
matrix.append([0] * y)
result (only last cell was changed in matrix):
z = set_matrix(z,index_2, 12)
print(z)
# [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 12]]

Google ORTools CP-SAT | Get list of index of 1's from a list of ortools-variables

I want to convert [0, 0, 1, 0, 1, 0, 1, 0] to [2, 4, 6] using ortools.
Where "2", "4", "6" in the second list are the index of "1" in the first list.
Using the below code I could get a list [0, 0, 2, 0, 4, 0, 6, 0]. How can I get [2, 4, 6]?
from ortools.sat.python import cp_model
model = cp_model.CpModel()
solver = cp_model.CpSolver()
work = {}
days = 8
horizon = 7
for i in range(days):
work[i] = model.NewBoolVar("work(%i)" % (i))
model.Add(work[0] == 0)
model.Add(work[1] == 0)
model.Add(work[2] == 1)
model.Add(work[3] == 0)
model.Add(work[4] == 1)
model.Add(work[5] == 0)
model.Add(work[6] == 1)
model.Add(work[7] == 0)
v1 = [model.NewIntVar(0, horizon, "") for _ in range(days)]
for d in range(days):
model.Add(v1[d] == d * work[d])
status = solver.Solve(model)
print("status:", status)
vec = []
for i in range(days):
vec.append(solver.Value(work[i]))
print("work",vec)
vec = []
for v in v1:
vec.append(solver.Value(v))
print("vec1",vec)
You should see this output on the console,
status: 4
work [0, 0, 1, 0, 1, 0, 1, 0]
vec1 [0, 0, 2, 0, 4, 0, 6, 0]
Thank you.
Edit:
I also wish to get a result as [4, 6, 2].
For just 3 variables, this is easy. In pseudo code:
The max index is max(work[i] * i)
The min index is min(horizon - (horizon - i) * work[i])
The medium is sum(i * work[i]) - max_index - min_index
But that is cheating.
If you want more that 3 variable, you will need parallel arrays of Boolean variables that indicate the rank of each variable.
Let me sketch the full solution.
You need to build a graph. The X axis are the variables. The why axis are the ranks. You have horizontal arcs going right, and diagonal arcs going right and up. If the variable is selected, you need to use a diagonal arc, otherwise an horizontal arc.
If using a diagonal arc, you will assign the current variable to the rank of the tail of the arc.
Then you need to add constraints to make it a contiguous path:
mass conservation at each node
variable is selected -> one of the diagonal arc must be selected
variable is not selected -> one of the horizontal arc must be selected
bottom left node has one outgoing arc
top right node has one incoming arc

How can "self" update original variable correctly? Recursion/Backtracking in the N-queens problem (Python)

This is my python program to solve the 8-queens problem. Everything is working except the final step of printing the solved board. I use recursion/backtracking to fill the board with queens until a solution is found. The board object that holds the solution is self, which is a reference to b1, so I assume that b1, the original board I initialized, would be updated to contain the final solved board, and would print the solution using printBoard. However, b1 is not being updated and is holding a failed board when I print it for some unknown reason.
edit: added placeQueen in solve
EMPTY = 0
QUEEN = 1
RESTRICTED = 2
class Board:
# initializes a 8x8 array
def __init__ (self):
self.board = [[EMPTY for x in range(8)] for y in range(8)]
# pretty prints board
def printBoard(self):
for row in self.board:
print(row)
# places a queen on a board
def placeQueen(self, x, y):
# restricts row
self.board[y] = [RESTRICTED for i in range(8)]
# restricts column
for row in self.board:
row[x] = RESTRICTED
# places queen
self.board[y][x] = QUEEN
self.fillDiagonal(x, y, 0, 0, -1, -1) # restricts top left diagonal
self.fillDiagonal(x, y, 7, 0, 1, -1) # restructs top right diagonal
self.fillDiagonal(x, y, 0, 7, -1, 1) # restricts bottom left diagonal
self.fillDiagonal(x, y, 7, 7, 1, 1) # restricts bottom right diagonal
# restricts a diagonal in a specified direction
def fillDiagonal(self, x, y, xlim, ylim, xadd, yadd):
if x != xlim and y != ylim:
self.board[y + yadd][x + xadd] = RESTRICTED
self.fillDiagonal(x + xadd, y + yadd, xlim, ylim, xadd, yadd)
# recursively places queens such that no queen shares a row or
# column with another queen, or in other words, no queen sits on a
# restricted square. Should solve by backtracking until solution is found.
def solve(self, col):
if col == -1:
return True
for i in range(8):
if self.board[i][col] == EMPTY:
temp = self.copy()
self.placeQueen(col, i)
if self.solve(col - 1):
return True
temp.board[i][col] = RESTRICTED
self = temp.copy()
return False
# deep copies a board onto another board
def copy(self):
copy = Board()
for i in range(8):
for j in range (8):
copy.board[j][i] = self.board[j][i]
return copy
b1 = Board()
b1.solve(7)
b1.printBoard()
I know that my actual solver is working, because when I add a printBoard like so:
if col == -1:
self.printBoard()
return True
in the solve method, a solved board is printed. In short, why is the self instance of a board not updating b1?
I believe your problem is related to redefining self in the solve method, andi'm not even sure why you're doing that.
See this question for more details: Is it safe to replace a self object by another object of the same type in a method?
Reassigning self like you're doing is not reassigning the "b1" reference. So when you reference b1 again and do printBoard, you're referencing a different object than what "self.printBoard()" will be referencing by the time solve is done.
I would step back and ask yourself why you're replacing self to begin with, and what this gains you. You likely don't need too and shouldn't be doing it either.
I'm not sure how this works since placeQueen is never called. As such, I don't see that adding a print as suggested presents a finished board (I see it as empty). [note: the latest update fixes this]
Using the restricted squares idea could work, but the way it's implemented here (without an undo option) is inefficient; copying a whole new Board object for every inner loop is very expensive. For all the trouble, we could just as well perform an iterative conflict check per move which at least saves the allocation and garbage collection costs of a new heap object.
As far as returning the completed board result, use a return value of self or self.board and None on failure rather than True and False.
A few other points:
Since solving a puzzle doesn't require state and we can (hopefully) agree that copying the board is inefficient, I'm not sure if there's much point in allowing an __init__ method. The class is nice as an encapsulation construct and we should hide static variables like EMPTY, QUEEN, etc inside the Board class regardless of whether the class is static or instantiated.
If you do decide to keep the class stateful, printBoard should not produce side effects--override __str__ instead.
Don't hardcode size literals such as 8 throughout the code; this makes the class rigid, difficult to maintain and prone to typos and off-by-one errors. Use len(self.board) instead and provide parameters liberally.
fillDiagonal doesn't need to be recursive. Consider using list comprehensions or numpy to simplify this matrix traversal logic.
Use snake_case variable names and docstrings instead of hashtag comments per PEP-8. If you feel compelled to write a comment like # restricts column, consider moving the relevant chunk to a function called restrict_column(...) and skip the comment.
Here's an initial rewrite that implements a few of these points:
class Board:
EMPTY = 0
QUEEN = 1
DIRS = [(x, y) for x in range(-1, 2) for y in range(-1, 2) if x]
def __init__ (self, size=8):
self.board = [[Board.EMPTY] * size for _ in range(size)]
def __str__(self):
return "\n".join(map(str, self.board))
def legal_from(self, row, col, dr, dc):
while row >= 0 and row < len(self.board) and \
col >= 0 and col < len(self.board[row]):
if self.board[row][col] != Board.EMPTY:
return False
row += dr; col += dc
return True
def legal_move(self, row, col):
return all([self.legal_from(row, col, *d) for d in Board.DIRS])
def solve(self, row=0):
if row >= len(self.board):
return self
for col in range(len(self.board[row])):
if self.legal_move(row, col):
self.board[row][col] = Board.QUEEN
if self.solve(row + 1):
return self
self.board[row][col] = Board.EMPTY
if __name__ == "__main__":
for result in [Board(i).solve() for i in range(9)]:
print(result, "\n")
Output:
[1]
None
None
[0, 1, 0, 0]
[0, 0, 0, 1]
[1, 0, 0, 0]
[0, 0, 1, 0]
[1, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 1]
[0, 1, 0, 0, 0]
[0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 0, 0, 1, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]

Finding area of subspace of 2d array?

I am trying to make a function that given a 2d array of either 0's or 1's and a set of coordinates, returns the area of the selected region with the same value as the given coordinates.
For example given the array:
[[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0]]
and the coordinates [0,0], it would return 5.
I have tried a simple DFS but am running into issues where it runs over the same spots multiple times, returning an abnormally large area like 330.
I actually figured this one out!
Basically it follows a BFS search looking only up, down, left, and right.
The part on finding the neighbours was taken from this answer to another question.
def find_area(arr, x, y):
queue = [(x,y)]
visited = []
w = len(arr[0])
h = len(arr)
area = 0
while len(queue) != 0:
curr = queue.pop(0)
print(curr)
x = curr[0]
y = curr[1]
if arr[curr[0]][curr[1]] == 0 and curr not in visited:
area += 1
visited.append(curr)
neighbors = [(x+a[0], y+a[1])
for a in [(-1,0), (1,0), (0,-1), (0,1)]
if ((0 <= x+a[0] < w) and (0 <= y+a[1] < h))]
for i in neighbors:
if i not in visited:
queue.append(i)
return area

Why my list is not changing the way it's supposed to?

I'm trying to build a method in my class Circle which gets a matrix (represented by a list of lists, each sublist represents a row) as an input.
The matrix has zero in every cell, and I'm supposed to place my circle in the center of the matrix and check if the (i,j) cell which represents the (i,j) point is contained in the circle, but for some reason I get a different output.
Here is an example:
mat = [[0 for j in range(5)] for i in range(7)]
Circle(40, 10, 1).draw(mat)
The output I expect is:
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]
But the output I get is:
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 1],
[0, 0, 0, 1, 1],
[0, 0, 0, 0, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]
Here's my code:
class Point():
""" Holds data on a point (x,y) in the plane """
def __init__(self, x=0, y=0):
assert isinstance(x,(int, float)) and isinstance(y,(int, float))
self.x = x
self.y = y
class Circle():
""" Holds data on a circle in the plane """
def __init__(self,*args):
if len(args)==2:
if isinstance(args[0],Point) and isinstance(args[1],(float,int)):
assert args[1]>0
self.center= args[0]
self.radius= args[1]
if len(args)==3:
assert args[2]>0
self.a=args[0]
self.b=args[1]
self.center= Point(self.a,self.b)
self.radius= args[2]
def contains(self,check):
if isinstance(check,(Point)):
if math.sqrt((self.center.x-check.x)**2 + (self.center.y-check.y)**2) <= self.radius:
return True
if isinstance(check,Circle):
test= math.sqrt((self.center.x-check.center.x)**2 + (self.center.x-check.center.x)**2)
if test < (abs((self.radius)-(check.radius))):
return True
else:
return False
def draw(self,mat):
n=len(mat)
m=len(mat[0])
newcircle=Circle((int(m/2)+1),(int(n/2)+1),self.radius)
for i,lst in enumerate(mat):
for j,val in enumerate(lst):
if newcircle.contains(Point(i,j)):
mat[i][j]=1
You're not placing your circle in the middle of the matrix.
newcircle=Circle((int(m/2)+1),(int(n/2)+1),self.radius)
should be
newcircle=Circle((int(n/2)),(int(m/2)),self.radius)
or possibly, since there is no need to use just integers here.
newcircle=Circle((n-1)/2.0,(m-1)/2.0,self.radius)
To draw a circle, you create a new circle in the center of the matrix, you check the cells that are inside and then you turn the value to 1 for those inside.
1) First, there is a problem with the function contains :
def contains(...):
if (cond1):
if (cond11)
return True
if (cond2):
if (cond21)
return True
else:
return False
If cond2 is true and cond21 is false, you will get a None.
To be more Pythonic, try:
def contains(...):
if (cond1) and (cond11):
return True
elif (cond2) and (cond21):
return True
else:
return False
You are sure in this case to have a True or a False.
2) Copy / Paste mistake
There is a copy / paste mistake in the function contains when instance is Circle.
You have forgotten to turn y into x.
3) Function draw
All you need for this function is the radius.
Be careful with the use of integers and floats; we have:
(int(5 / 2)) == (5 / 2) != (5 / 2.)
To be sure to have a float, write the divisor as a float 2. instead of 2
Your circle is created on a matrix which rows and columns indices start with 1 if you define the center using int(len(mat) / 2.) + 1.
Don't forget that enumerate indices starts at 0, not 1.
So, int((len(mat) - 1) / 2.) + 1 (which is the same as len(mat) / 2) would be more accurate.
Seriously, Taha!

Categories

Resources