I am returning the length of the shortest path if there's is one. Otherwise, I return -1.
I'm trying to print the matrix in such a way that all visited nodes that were a part of the shortest path are marked with '$' instead of '1'. However, I'm not able to do so.
def Path(grid):
#print(grid)
start = (0, 0)
queue = collections.deque([[start]])
seen = set([start])
while queue:
path = queue.popleft()
x, y = path[-1]
if y == 7 and x == 7:
#print(path)
for (i, j) in path:
grid[i][j] = '$'
print(grid)
return len(path)
for x2, y2 in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1):
if (grid[y2][x2] != 0) and (x2, y2) not in seen:
queue.append(path + [(x2, y2)])
seen.add((x2, y2))
print(grid)
return -1
The issue is in how you address grid:
When looking for the next available move, you do this test:
grid[y2][x2] != 0
where (x2, y2) is potentially added to the path.
But when you finally assign the $, you write:
grid[i][j] = '$'
where (i, j) is the tuple taken from the path.
This is not consistent. You should swap the coordinate position in one of the two grid[][] expressions to make them consistent.
Related
Need help with finding shortest distance in a binary maze, which is a list of lists matrix, where 0 is an empty cell and 1 is a wall. The maze has x,y starting coordinates that default to 0,0 and it's end point is always bottom right corner. Shortest distance always includes the starting point (i.e., if there are four steps needed from the starting point, shortest distance will be 5)
I need to be using backtracking algorithm. So far I could come up with a function that determines if there is an escaping path at all. It works well:
def is_solution(maze, x=0, y=0):
n = len(maze)
m = len(maze[0])
if x == n - 1 and y == m - 1:
return True
maze[x][y] = 1
result = False
for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)]:
if 0 <= a < n and 0 <= b < m and maze[a][b] == 0:
result = result or is_solution(maze, a, b)
maze[x][y] = 0
return result
maze = [
[0, 0, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0]
]
is_solution(maze)
The above will result to True.
Now I am really struggling with finding the shortest distance. I think it should be relatively easy to update the code above so it showed distance if there is a path and inf if there isn't one. But I got stuck. In the example above shortest distance would be 6 (including the starting point)
I also need to add code to be able to get a list of all shortest distances and coordinates of each step in a list of lists format like [[(0, 0), (0, 1), (1, 1), (1, 2), (1, 3), (2, 3)]] . In this case there is only one path, but if there were two of distance six, that list would include also the list of second shortest path as well.
Thank you.
Simple change to your code to track shortest path
Shortest Path
def min_solution(maze, x = 0, y = 0, path = None):
def try_next(x, y):
' Next position we can try '
return [(a, b) for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] if 0 <= a < n and 0 <= b < m]
n = len(maze)
m = len(maze[0])
if path is None:
path = [(x, y)] # Init path to current position
# Reached destionation
if x == n - 1 and y == m - 1:
return path
maze[x][y] = 1 # Mark current position so we won't use this cell in recursion
# Recursively find shortest path
shortest_path = None
for a, b in try_next(x, y):
if not maze[a][b]:
last_path = min_solution(maze, a, b, path + [(a, b)]) # Solution going to a, b next
if not shortest_path:
shortest_path = last_path # Use since haven't found a path yet
elif last_path and len(last_path) < len(shortest_path):
shortest_path = last_path # Use since path is shorter
maze[x][y] = 0 # Unmark so we can use this cell
return shortest_path
maze = [
[0, 0, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0]
]
t = min_solution(maze)
if t:
print(f'Shortest path {t} has length {len(t)}')
else:
print('Path not found')
Output:
Shortest path [(0, 0), (0, 1), (1, 1), (1, 2), (1, 3), (2, 3)] has length 6
All Paths
def all_paths(maze, x = 0, y = 0, path = None):
'''
All paths through Maze as a generator
'''
def try_next(x, y):
' Next position we can try '
return [(a, b) for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] if 0 <= a < n and 0 <= b < m]
n = len(maze)
m = len(maze[0])
if path is None:
path = [(x, y)]
# Reached destionation
if x == n - 1 and y == m - 1:
yield path
else:
maze[x][y] = 1 # Mark current position so we won't use this cell in recursion
# Recursively find pat
for a, b in try_next(x, y):
if not maze[a][b]:
yield from all_paths(maze, a, b, path + [(a, b)]) # Solution going to a, b next
maze[x][y] = 0 # Unmark so we can use this cell
maze = [[0, 0, 0],
[1, 0, 0],
[1, 1, 0]]
for t in all_paths(maze):
print(f'path {t} has length {len(t)}')
Output
path [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)] has length 5
path [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)] has length 5
This is my solution to given a matrix m x n, find the minimum path sum. It works fine however, I'm not sure how to modify it to see the path / save it in some list, how can this be done?
def get_path(matrix, x, y, seen):
if (x, y) in seen:
return seen[x, y]
x_end = len(matrix) - 1
y_end = len(matrix[0]) - 1
current = matrix[x][y]
if x == x_end and y == y_end:
return current
possible_moves = []
if x < len(matrix) - 1:
possible_moves.append([x + 1, y])
if y < len(matrix[0]) - 1:
possible_moves.append([x, y + 1])
results = [
current + get_path(matrix, *possible, seen) for possible in possible_moves
]
current_best = min(results)
seen[x, y] = current_best
return current_best
You don't need to.
After get_path returns, start from 0,0 look for a move where seen[x', y'] = seen[x,y] - matrix[x,y].
If you have equality (both moves work) pick whatever you want (equals paths).
Keep going until you reach the end.
I'm given a list with coordinates of n points, let's say:
points = [(1, 2), (2, 3), (3, 4)]
And I need to check if all of them lie on the same line. I also decided to consider 3 cases to avoid dividing by zero when x1 == x2.
So here's my code in Python:
# 2 points always lie on a line
if n <= 2:
print("yes")
else:
# leave only unique points
points = list(set(points))
x1, y1 = points[0]
x2, y2 = points[1]
# if two points have the same x coordinate
# then they lie on a vertical line and
# all other points have to have the same x coordinate too
if x2 == x1:
for i in range(2, len(points)):
if points[i][0] != x1:
print("no")
break
else: print("yes")
# same with a horizontal line
elif y2 == y1:
for i in range(2, len(points)):
if points[i][1] != y1:
print("no")
break
else: print("yes")
else:
a = (y2-y1)/(x2-x1)
b = y2 - a * x2
for i in range(2, len(points)):
x, y = points[i]
if (y != a * x + b):
print("no")
break
else: print("yes")
It seems that I have a mistake somewhere in the code but I don't really understand what it is.
Using cross product of vectors eliminates the complexity of having to deal with special cases where division by zero might happen. Three points are collinear if the cross product of the vectors formed by the two vectors defined by the 3 points is equal to zero:
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def cross(self, other):
return self.x * other.y - self.y * other.x
def are_collinear(three_points):
a, b, c = three_points
# better use math.isclose than == to check for floats
return math.isclose((b-a).cross(c-a), 0.0)
points = [Point(1, 2), Point(2, 3), Point(3, 4)]
print(are_collinear(points))
# True
points = [Point(1, 2), Point(3, 3), Point(3, 4)]
print(are_collinear(points))
# False
From any point in the list (e.g. first one) if all other points have the same slope with that one, then they are on the same line.
def sameLine(points):
x0,y0 = points[0]
points = [ (x,y) for x,y in points if x != x0 or y != y0 ] # Other points
slopes = [ (y-y0)/(x-x0) if x!=x0 else None for x,y in points ] # None for vertical Line
return all( s == slopes[0] for s in slopes)
This function is supposed to take in a point parameter which will be used to find the closest point to it that lies on the line segment object. In the example assertion code the function getClosestPoint(Point()) takes Point(10, 0) as parameters and should return Point(5,5) as the closest point to Point(10, 0) that is on the line of l1 = Line(Point(5,5), Point(20,35)) With the endpoints being A Point(5,5), B Point(20,35) I'm not sure how to go about solving this problem. My current solution will return (4,3) and that is not on the line segment but is on the line.
from point import Point
import math
class Line:
def __init__(self,aPoint=Point(), bPoint=Point()):
self.firstPoint = aPoint
self.secondPoint = bPoint
def getClosestPoint(self,point=Point()):
m1 = self.getSlope()
m2 = -1 / float(m1)
b1 = self.p1.y - m1 * self.p1.x
b2 = point.y - m2 * point.x
x = float(b2 - b1) / float(m1 - m2)
y = m1 * x + b1
return Point(x, y)
if __name__ == "__main__":
p1 = Point(5,5)
p2 = Point(20,35)
l1 = Line(p1,p2)
assert l1.getClosestPoint(Point(10,0)) == Point(5,5)
assert l2.getClosestPoint(Point(25/2,25/2)
class Point:
def __init__(self,x=0,y=0):
self.x = x
self.y = y
The general answer is to project the point onto the line.
One way to see it is to transform the point into the reference frame defined by your segment (p1 is the new origin (0, 0), p2 the new (1, 0)).
Then, you get rid of the new y coordinate (that's where the actual projection occurs) and transform the new point (x, 0) back into the original frame.
Concretely, you'll have to find the transformations.
The second one, from the new space into the original space is easy to write (just draw it on paper, you'll see):
x = (x2 - x1) * nx + (y2 - y1) * ny + x1
y = (y1 - y2) * nx + (x2 - x1) * ny + y1
But you can invert these equations to find (nx, ny) that correspond to a point (x, y).
When you do that, and assuming neither of us has made any mistakes nor typo, you should get something like:
dx = x2 - x1
dy = y2 - y1
d2 = dx*dx + dy*dy
nx = ((x3-x1)*dx + (y3-y1)*dy) / d2
return (dx*nx + x1, dy*nx + y1)
edit: If you actually have to find the closest point on the segment instead of the line, it is easy to find because if the projection falls within the segment, you have 0 <= nx <= 1 (it's a necessary and sufficient condition).
Before the return statement, you can just force nx to stay in this interval:
nx = min(1, max(0, nx))
reedit:
The statement above is equivalent to:
if nx<0:
nx = 0
if nx>1:
nx = 1
This way, the projection of the point on the line (which can be outside the segment) gets pushed back inside the segment (defined by 0 <= nx <= 1) at the closest point.
I have just finished with my food fill algorithm in python. It runs on an N*N matrix filled with integers. I would like to somehow animate it's workings. Is it somehow possible on the console? I think of something like updateing the nodes with a wait() inbetween the updates.
You could use something like this:
#! /usr/bin/python3
import time
m = [ [c for c in line] for line in '''............................
..XXXXXXXXXX...........XXX..
..X........X...........X.X..
..XXXXXX...X....XXXXXXXX.X..
.......X...X....X........X..
....XXXX...XXXXXX........X..
....X....................X..
....X.................XXXX..
....XXXXXXXXXXXXXXXXXXX.....'''.split ('\n') ]
def flood (matrix, start):
maxX = len (matrix [0] )
maxY = len (matrix)
tbf = [start]
while tbf:
x, y = tbf [0]
tbf = tbf [1:]
if x < 0 or x >= maxX or y < 0 or y >= maxY: continue
if matrix [y] [x] == 'X': continue
matrix [y] [x] = 'X'
tbf += [ (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1) ]
print ('\x1b[0J\x1b[1;1H') #Clear screen and position cursor top left
for line in matrix: print (''.join (line) )
time.sleep (.2)
#flood (m, (0, 0) )
flood (m, (4, 2) )
This should work on any console that supports ANSI escape sequences (CSI). You can use the same CSI codes for outputting colours (Wiki).