How can I speed up/fix this shortest path grid traversal? - python

I'm working on writing a program to traverse a grid from the bottom left to the top right where valid moves are moving to the right by 1, moving up by 1, or jumping up or right by an amount designated in the jump array. The inputs are the grid in a 2D array format, dimensions of the grid: X and Y, and an array of jumps where the jumps have to be completed in order. The output is the shortest path where the length of the path is the sum of all numbers touched including bottom left and top right.
Example input:
Grid:
[9 9 1]
[9 9 1]
[3 9 1]
Jumps: [1 1] X:3, Y:3
The output for this would be 5 because we start at (0,0) which is 3 and then use the first 1 block jump to (2, 0) which is 1 and then the second one block jump to (2, 2) which is 1 so 3+1+1 = 5. If the jumps array was only [1] then the output would be 6 since we would have to move from (2,0) to (2,1) to (2,2).
This is my first solution, which seems to work for smaller inputs but just runs forever on larger inputs:
def get(grid, x, y, endX, endY):
if (x > endX or y > endY):
return False
return grid[y][x]
def fly(x, y, grid, jumps, endX, endY):
if x > endX or y > endY:
return float('inf')
if (x == endX and y == endY):
return get(grid, endX, endY, endX, endY)
flyup = fly(x, y+1, grid, jumps, endX, endY)
flyright = fly(x+1, y, grid, jumps, endX, endY)
if (len(jumps) > 0):
jumpup = fly(x, y+jumps[0]+1, grid, jumps[1:], endX, endY)
jumpright = fly(x+jumps[0]+1, y, grid, jumps[1:], endX, endY)
temp = min(flyup, flyright, jumpup, jumpright)
return get(grid, x, y, endX, endY) + temp
else:
temp = min(flyup, flyright)
return get(grid, x, y, endX, endY) + temp
fly(0, 0, grid, jumps, X-1, Y-1)
This is another solution I wrote using DP (or whatever my understanding of DP is since I just learned it) but this one seems to only work on certain inputs and I can't identify a pattern.
def fly2(x, y, grid, jumps):
dp = [[0 for i in range(len(grid[0]))] for j in range(len(grid))]
for row in range(len(grid)):
for col in range(len(grid[0])):
if row == 0 and col == 0:
dp[row][col] += get2(grid, col, row)
else:
flyup = float('inf') if row==0 else dp[row-1][col]
flyright = float('inf') if col==0 else dp[row][col-1]
jumpup = float('inf') if row < jumps[0]+1 else dp[row-jumps[0]-1][col]
jumpright = float('inf') if col < jumps[0]+1 else dp[row][col-jumps[0]-1]
shortest = min(flyup, flyright, jumpup, jumpright)
if min == jumpup or min == jumpright:
jumps = jumps[1:]
dp[row][col] += get2(grid, col, row) + shortest
return dp[len(grid)-1][len(grid[0])-1]
I need some help speeding up the first one or figuring out what's wrong with the second one or maybe get some other ideas on how I can write this efficiently. Thanks

It seems to me that the dp should have another dimension for the current jump index. Generally,
dp[y][x][j] = Grid[y][x] + min(
dp[y + 1][x][j],
dp[y][x - 1][j],
dp[y + jump[j-1] + 1][x][j-1],
dp[y][x - jump[j-1] - 1][j-1]
)
where j is the current index in the jump array.
(I think the question description uses (x, y) coordinate notation in the example. I used [y][x] as [row][column], common for programming two dimension array access.)

The following implementation uses the grid and the remaining jumps as the state variables:
import numpy as np
def shortest(grid, jumps):
rows, cols = grid.shape
if (rows, cols) == (1, 1): # if grid is just a single number
return float('inf') if jumps else grid[0, 0]
candidates = [] # store results from deeper calls
if rows > 1:
candidates.append(shortest(grid[:-1, :], jumps)) # up by one
if jumps and rows > jumps[0] + 1: # jump to the above if possible
candidates.append(shortest(grid[:-(jumps[0] + 1), :], jumps[1:]))
if cols > 1:
candidates.append(shortest(grid[:, 1:], jumps)) # right by one
if jumps and cols > jumps[0] + 1: # jump to the right if possible
candidates.append(shortest(grid[:, (jumps[0] + 1):], jumps[1:]))
return grid[-1, 0] + min(candidates)
grid = np.array([[9, 9, 1], [9, 9, 1], [3, 9, 1]])
jumps = [1, 1]
print(shortest(grid, jumps)) # 5
Usage of np.array is just for simplicity of slicing.

Related

Find sum of elements within the area of 45 degree rotated rectangle

Given a matrix of integers, we'd like to consider the sum of the elements within the area of a 45° rotated rectangle.
More formally, the area is bounded by two diagonals parallel to the main diagonal and two diagonals parallel to the secondary diagonal.
The dimensions of the rotated rectangle are defined by the number of elements along the borders of the rectangle. Given integers a and b representing the dimensions of the rotated rectangle, and matrix (a matrix of integers), your task is to find the greatest sum of integers contained within an a x b rotated rectangle.
Note: The order of the dimensions is not important - consider all a x b and b x a rectangles.
matrix = [[1, 2, 3, 4, 0],
[5, 6, 7, 8, 1],
[3, 2, 4, 1, 4],
[4, 3, 5, 1, 6]]
a = 2, and b = 3, the output should be rotatedRectSum(matrix, a, b) = 36.
I need help to understand how range(w - 1, rows - h + 1) and range(0, cols - (h + w - 1) + 1) are calculated?
def rotatedRectSum(matrix, a, b):
rows, cols = len(matrix), len(matrix[0])
maxArea = float("-inf")
# go through possible rectangles along both diagonals
for w, h in [(a, b), (b, a)]:
# go through possible "anchors", which is the left top coordinate of the rectangle candidate
for i in range(w - 1, rows - h + 1):
for j in range(0, cols - (h + w - 1) + 1):
area = 0
# sum up the long diagonals
for p in range(w): # go to next long diagonal
for q in range(h): # go down current diagonal
area += matrix[i - p + q][j + p + q]
# sum up the short diagonals
k, l = i, j + 1 # note that short diagonals have one less element than long diagonals
for p in range(w - 1):
for q in range(h - 1):
area += matrix[k- p + q][l + p + q]
if (area > maxArea): maxArea = area
return maxArea
More Explanation: Click Here
I need help to understand how range(w - 1, rows - h + 1) and range(0, cols - (h + w - 1) + 1) are calculated?
What the author calls anchor is the leftmost cell in the rotated rectangle that is considered. Below an orange-marked anchor and the corresponding rectangle that goes with it (width is 2, height is 3):
i is the row in which the anchor can be. It is clear that when the width is 2, we cannot have the anchor in the top row as we need at least one row above it to have the necessary space for the top-corner of the rectangle. As the row in which the anchor resides already counts as 1, we need w - 1 more rows above it so to have enough room for the upper part of the rotated rectangle. This explains why the first range starts with w - 1. It means that the anchor cannot be in the first w - 1 rows, and the least possible row where the anchor can be placed is the one with index w - 1.
With a similar reasoning we can deduce how many rows there should at least be below the anchor's row. There should be at least h - 1 rows below it, so to have enough room for the bottom part of the rotated rectangle. This means the greatest possible row index for the anchor's row is rows - h. That means we should have a range(w - 1, rows - h + 1) to iterate all possible row indices for the anchor (remember that the second value given to range is not included in the values it yields).
The reasoning for the second range is the same, but now it concerns the columns where the anchor could possibly be (i.e. the value for j). The anchor can always be in the very first column, as there is no part of the rectangle that comes at the left of it. So that means that valid column numbers for the anchor start at column index 0, hence range(0, ...)
Finally, the number of columns that follows after the anchor's column is w - 1 (for reaching the column with the top peek of the rectangle) and another h - 1 (for reaching from there on the right most cell in the rectangle). So that gives a total of w + h - 2 columns that need to be available at the right of the anchor's column. This means the last possible column index for an anchor is cols - (w + h - 1), and so the range for possible column indices should be defined as range(0, cols - (w + h - 1) + 1).
I hope this clarifies it.
def rotatedRectSum(matrix, a, b):
rows, cols = len(matrix), len(matrix[0])
maxArea = float("-inf")
# go through possible rectangles along both diagonals
for w, h in [(a, b), (b, a)]:
# go through possible "anchors", which is the left top coordinate of the rectangle candidate
for i in range(w - 1, rows - h + 1):
for j in range(0, cols - (h + w - 1) + 1):
area = 0
# sum up the long diagonals
for p in range(w): # go to next long diagonal
for q in range(h): # go down current diagonal
# print(matrix[i - p + q][j + p + q])
area += matrix[i - p + q][j + p + q]
# sum up the short diagonals
k, l = i, j + 1 # note that short diagonals have one less element than long diagonals
for p in range(w - 1):
for q in range(h - 1):
area += matrix[k- p + q][l + p + q]
if (area > maxArea): maxArea = area
return maxArea

Find indices of elements in all "eight directions" of a Python 2D Array/Matrix

How can I find the indices of ALL the elements in all the eight directions (left, right, up, down, left-upper, right-upper, left-lower, right-lower) of a given 2D Matrix/Array?
For example in the given matrix below, I'm looking to extract the elements marked X for the input 0 and so on...:
X**X**X
*X*X*X*
**XXX**
XXX0XXX
**XXX**
X0XXXXX
XXX****
*X*X***
*X**X**
*X***X*
I used this lambda function to get the list of all the adjacent elements in all eight directions.
X = len(grid)
Y = len(grid[0])
neighbors = lambda x, y : [(x2, y2) for x2 in range(x-1, x+2)
for y2 in range(y-1, y+2)
if (-1 < x < X and
-1 < y < Y and
(x != x2 or y != y2) and
(0 <= x2 < X) and
(0 <= y2 < Y))]
**XXX**
**X0X**
**XXX**
*******
I want to be able to expand the above to get the above.
The following will provide the indexes in all eight directions:
from itertools import product
def valid_indexes(lst, row, col):
return row in range(len(lst)) and col in range(len(lst[0]))
# Queen's view since it's the same as what a queen in chess can 'view'
def queens_view(lst, row, col, dr=0, dc=0):
if dr or dc:
yield (row, col)
if valid_indexes(lst, row+dr, col+dc):
yield from queens_view(lst, row+dr, col+dc, dr, dc)
else:
for dr, dc in product([-1, 0, 1], [-1, 0, 1]):
if dr or dc and valid_indexes(lst, row+dr, col+dc):
yield from queens_view(lst, row+dr, col+dc, dr, dc)
start_row, start_col = 3, 4 # Index of the '0' in your list
for row, col in queens_view(lst, start_row, start_col):
print(row, col)
# Do what you like with the index
You can make a functions that 'shoots' in every direction from the desired point until the end of the grid is reached. Returning all the points found:
def direction_points(g_len_x, g_len_y, point_x, point_y):
result = []
directions = [
[0, -1], # up
[1, -1], # up right
[1, 0], # right
[1, 1], # down right
[0, 1], # down
[-1, 1], # down left
[-1, 0], # left
[-1, -1], # left up
]
for direction in directions:
x = point_x
y = point_y
end_reached = False
while not end_reached:
x = x + direction[0]
y = y + direction[1]
if (0 <= x < g_len_x) and not end_reached:
if (0 <= y < g_len_y) and not end_reached:
result.append([x, y])
else:
end_reached = True
else:
end_reached = True
return result
With that you can create the lines or grid:
grid_len_x = 10
grid_len_y = 12
mid_point_x = 4
mid_point_y = 6
points = direction_points(grid_len_x, grid_len_y, mid_point_x, mid_point_y)
lines = []
for y in range(grid_len_y):
line = ''
for x in range(grid_len_x):
if [x, y] in points:
line += 'X'
else:
if x == mid_point_x and y == mid_point_y:
line += '0'
else:
line += '*'
lines.append(line)
for line in lines:
print(line)
Result
****X*****
****X****X
X***X***X*
*X**X**X**
**X*X*X***
***XXX****
XXXX0XXXXX
***XXX****
**X*X*X***
*X**X**X**
X***X***X*
****X****X

Iterating through list in 'spiral'

I have an image that looks like this:
I've converted it into a 2D list. What's the best way to iterate through this list in a 'spiral', starting at the top left and ending in the centre. The goal is to read in all non-black pixels.
Here is code I have that progresses through spirals, starting in the upper left and going clockwise. It is naive (doesn't make use of that we know there are spirals) and simply prints out the coordinates, but I hope you can modify it to what you need.
I've checked several edge cases, because you need to make sure (0,1,2,3 mod 4) x (0,1,2,3 mod 4) all work. And wide spirals and squarish spirals need to be considered.
def do_stuff_with(my_array, x, y):
print("Do stuff with", x, ",", y) #obviously, you will want to return or manipulate something. But this code is just about traversing the spiral.
def spiral(my_array, width, height):
x_left = 0
x_right = width
y_top = 0
y_bottom = height
x_c = y_c = 0
print("Bounding box {0},{1} -> {2},{3}".format(x_left, y_top, x_right, y_bottom))
if x_left >= x_right or y_top >= y_bottom:
print("Invalid spiral range.")
return
while x_left < x_right and y_top < y_bottom:
#print("Going right")
for i in range(x_left, x_right):
do_stuff_with(my_array, i, y_top)
#print("Going down")
for i in range(y_top + 1, y_bottom):
do_stuff_with(my_array, x_right - 1, i)
if y_bottom - y_top > 1: # this just prevents us from revisiting a square in, say, a 5x7 spiral
#print("Going left")
for i in range(x_right - 2, x_left, -1):
do_stuff_with(my_array, i, y_bottom - 1)
if x_right - x_left > 1: # this just prevents us from revisiting a square in, say, a 7x5 spiral
#print("Going up")
for i in range(y_bottom - 1, y_top + 1, -1):
do_stuff_with(my_array, x_left, i)
# we need to fill in the square that links an outer spiral with an inner spiral.
if x_right - x_left > 2 and y_bottom - y_top > 4:
do_stuff_with(my_array, x_left + 1, y_top + 2)
x_left += 2
x_right -= 2
y_top += 2
y_bottom -= 2
print("Top/bottom overlap", y_top >= y_bottom)
print("Left/right overlap", x_left >= x_right)
def test_spirals(xi, yi, xf, yf):
'''an exhaustive test to make sure different rectangles/spirals work'''
for x in range(xi, xf):
for y in range(yi, yf):
print(x, y, "spiral test")
my_array = []
for z in range(0, y):
my_array.append([0] * x)
spiral(my_array, x, y)
# squarish tests: it seems like the main cases are (0/1/2/3 mod 4, 0/1/2/3 mod 4) so these 16 should knock everything out
test_spirals(4, 4, 8, 8)
# rectangular tests--yes, this overlaps the above case with 5x(6/7) but we want to try all possibilities mod 4 without having too much data to read.
#test_spirals(2, 6, 6, 10)
Let me know if you need or want clarifications.
ETA: here is some pseudocode for if you know you are reading in a spiral, but I think it is a big assumption. Also, this pseudocode is untested. But the idea is: go right til you hit a wall or black square, then down, then left, then up. Then repeat. Also, check for unnecessary trackbacks that may cause you to loop at the innermost line near the end.
def traverse_known_spiral(myary, width, length):
do_stuff(0, 0)
x_c = 0
y_c = 0
while True:
x_c_i = x_c
y_c_i = y_c
while x_c < width - 1 and myary[x_c+1][y_c] == WHITE:
do_stuff(x_c+1, y_c)
x_c += 1
while y_c < height - 1 and myary[x_c][y_c+1] == WHITE:
do_stuff(x_c, y_c+1)
y_c += 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move right or down, then there is nothing more to do
x_c_i = x_c
y_c_i = y_c
if y_c_i != y_c: # if we didn't go down, then we'd be going backwards here
while x_c > 0 and myary[x_c-1][y_c] == WHITE:
do_stuff(x_c-1, y_c)
x_c -= 1
if x_c_i != x_c: # if we didn't go right, then we'd be going backwards here
while y_c > 0 and myary[x_c-1][y_c-1] == WHITE:
do_stuff(x_c, y_c-1)
y_c -= 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move left or up, then there is nothing more to do

Breadth First Search Optimisation

I've been working on some introductory Graph Theory lately, and came across the following problem statement:
Given a 2D matrix as input, representing a maze (where 'E' is the start of the matrix and 'S' is the end), find the length of the shortest path from E to S. Walls in the maze are represented by '#', while accessible spaces are indicated with '.'. It's also a given that the outer edge of the matrix is covered with walls. If no path exists from E to S, return -1.
Since the graph isn't weighted, I tried implementing a BFS algorithm, using deque. However, when the mazes start hitting around 500x500, execution time hits 10s, and when I try to go up to 1000x1000, it's obviously a lot worse.
Here's my code:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return tuple([x, y])
start = find_start(maze)
queue = deque([[start]])
seen = {start}
while queue:
path = queue.popleft()
x, y = path[-1]
if maze[y][x] == endchar:
return len(path)-1
for (x2, y2) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 < x2 < width-1 and 0 < y2 < height-1 and maze[y2][x2] != wall and (x2, y2) not in seen:
queue.append(path + [(x2, y2)])
seen.add((x2, y2))
return -1
I found some very useful answers on the site so far, but none of the current ones seem to give any other optimizations that I haven't implemented yet...
Thanks!
EDIT: Thanks to the lovely person who edited my question to make the key words pop :). Here's an example of a matrix that you can run the algorithm on:
#####
#E#S#
#.#.#
#...#
#####
This should return the value 6.
EDIT2: Fixed some small mistakes.
As suggested in the comments, you don't have to store the paths. Try this:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return (x, y, 0)
start = find_start(maze)
queue = deque([start])
seen = set()
while queue:
x, y, d = queue.popleft()
if not 0 <= x < width:
continue
if not 0 <= y < height:
continue
if maze[y][x] == wall:
continue
if maze[y][x] == endchar:
return d
if (x, y) in seen:
continue
seen.add((x, y))
queue.append((x+1, y, d+1))
queue.append((x-1, y, d+1))
queue.append((x, y+1, d+1))
queue.append((x, y-1, d+1))
return -1
maze = [x for x in """
#####
#E#S#
#.#.#
#...#
#####
""".split('\n') if x]
print shortest_path_1(maze)

How to get diagonals of nested list that pass through a given point - Python

I have a 2d list like this:
thelist=[[0,0,0,0,0,0],
[1,0,0,0,0,0],
[0,1,0,0,0,0],
[0,0,1,0,0,0], # (3,2) is the 1 in this row
[0,0,0,1,0,0],
[0,0,0,0,1,0]]
And I'm trying to get the 2 diagonals of the 2d list that pass through given coordinates. In the list above, if the coordinates were (3,2), then the two lists would be [1,1,1,1,1] and [0,0,1,0,0,0].
I've seen solutions where I could get all the diagonals or a specific diagonal, but I have not been able to find a solution online that can get the diagonals that pass through a point. Here's what I've tried:
def find_diagonals(thelist,coor):
twodiag=[[],[]]
coor1=[coor[0]-min(coor),coor[1]-min(coor)]
coor2=[coor[0]-min(coor),coor[1]+min(coor)]
while True:
try:
twodiag[0].append(thelist[coor1[0]][coor1[1]])
coor1[0]+=1
coor1[1]+=1
except IndexError:
break
while True:
try:
twodiag[1].append(thelist[coor2[0]][coor2[1]])
coor2[0]+=1
coor2[1]-=1
except IndexError:
break
return twodiag
However, it only returns one diagonal correctly. How can either fix this code or solve the problem in another way? If anything is unclear, I'll be happy to answer it in the comments. Thanks!
solution using numpy:
import numpy as np
thelist=[[0,0,0,0,0,0],
[1,0,0,0,0,0],
[0,1,0,0,0,0],
[0,0,1,0,0,0], # (3,2) is the 1 in this row
[0,0,0,1,0,0],
[0,0,1,0,1,0]]
lst = matrix = np.array(thelist)
i, j = 3, 2 #position of element
major = np.diagonal(lst, offset=(j - i))
print(major)
minor = np.diagonal(np.rot90(lst), offset=-lst.shape[1] + (j + i) + 1)
print(minor)
Output:
[1 1 1 1 1]
[0 0 0 1 0 0]
width, height = len(thelist[0]), len(thelist)
size = max(width, height)
valid_x, valid_y = range(width), range(height)
pos = (3,2)
x, y = pos
diag1 = [thelist[i][i-x+y] for i in range(size) if i-x+y in valid_y]
diag2 = [thelist[i][x+y-i] for i in range(size) if x+y-i in valid_y]
print (diag1, diag2)
# [1, 1, 1, 1, 1] [0, 0, 0, 1, 0, 0]
The best solution is easy linear function:
def find_diagonals(input_list, coordinates):
diagonals = [[], []]
row_coordinate, column_coordinates = coordinates
for row_number, row in enumerate(input_list):
diagonal_point = column_coordinates-row_coordinate+row_number
if 0 <= diagonal_point <= len(row):
diagonals[0].append(row[diagonal_point])
diagonal_point = column_coordinates+row_coordinate-row_number
if 0 <= diagonal_point <= len(row):
diagonals[1].append(row[diagonal_point])
return diagonals
You can do this calculating the offsets:
def get_diagonals(alist, coordinates):
# Diagonal 1
start = coordinates
while start[0] > 0 and start[1] > 0:
start = (start[0] - 1, start[1] - 1)
diag1 = []
index = start
while index[0] < len(alist) and index[1] < len(alist[0]):
diag1.append(alist[index[0]][index[1]])
index = (index[0] + 1, index[1] + 1)
# Diagonal 2
start = coordinates
while start[0] < len(alist) - 1 and start[1] > 0:
start = (start[0] + 1, start[1] - 1)
diag2 = []
index = start
while index[0] >= 0 and index[1] < len(alist[0]):
diag2.append(alist[index[0]][index[1]])
index = (index[0] - 1, index[1] + 1)
return diag1, diag2
thelist=[[0,0,0,0,0,0],
[1,0,0,0,0,0],
[0,1,0,0,0,0],
[0,0,1,0,0,0], # (3,2) is the 1 in this row
[0,2,0,1,0,0],
[3,0,0,0,1,0]]
coord = (3,2)
get_diagonals(thelist, coord)
> ([1, 1, 1, 1, 1], [3, 2, 1, 0, 0, 0])
The code is straight forward and calculates positions within the matrix that are on the diagonal one by one.
edit: fixed off-by-one error

Categories