I try to write a recursive function that accepts a set of arrays of integers and returns the path from the upper left corner to the bottom right, passing only between neighboring cells, where each new cell must be substantially larger than the previous cell.
To get exactly the route, I need to check the four adjacent cells in the following order: right, down, left, and up.
def exit_rectangle_position(array, row, col, l):
if col + 1 < len(array) and array[row][col+1] > array[row][col]:
l.append([row,col+1])
exit_rectangle_position(array, row, col+1, l)
elif row + 1 < len(array) and array[row+1][col] > array[row][col]:
l.append([row+1,col])
exit_rectangle_position(array, row+1, col, l)
elif col - 1 >= 0 and array[row][col-1] > array[row][col]:
l.append([row,col-1])
exit_rectangle_position(array, row, col-1, l)
elif row - 1 >= 0 and array[row - 1][col] > array[row][col]:
l.append([row-1,col])
exit_rectangle_position(array, row-1, col, l)
def exit_rectangle(array):
l = []
l.append([0,0])
exit_rectangle_position(array,0,0,l)
if [len(array)-1, len(array)-1] in l:
return l
return []
The problem is that when I get stuck, I do not know how to get back from where I started.
For example the array
print(exit_rectangle([[1,2,3],[2,0,5],[3,4,5]]))
I have to return
# [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2]]
But I get
# []
Recursive DFS works well here:
def exit_rectangle(a, x=None, y=None, seen=None):
if x is None:
x = 0
if y is None:
y = 0
if seen is None:
seen = set()
if x + 1 == len(a[0]) and y + 1 == len(a):
# Exit found
return [(x, y)]
# Maybe we've been here before
if (x, y) in seen:
return None
seen.add((x, y))
# Go right
if x + 1 < len(a[0]) and a[y][x] < a[y][x+1]:
path = exit_rectangle(a, x+1, y, seen)
if path is not None:
return [(x, y)] + path
# Go left
if 0 < x and a[y][x] < a[y][x-1]:
path = exit_rectangle(a, x-1, y, seen)
if path is not None:
return [(x, y)] + path
# Go up
if 0 < y and a[y][x] < a[y-1][x]:
path = exit_rectangle(a, x, y-1, seen)
if path is not None:
return [(x, y)] + path
# Go down
if y + 1 < len(a) and a[y][x] < a[y+1][x]:
path = exit_rectangle(a, x, y+1, seen)
if path is not None:
return [(x, y)] + path
# Dead end
return None
print(exit_rectangle([[1,2,3],[2,0,5],[3,4,5]]))
Related
I need to find the biggest sequence of zeros next to each other (up down left right).
for example in this example the function should return 6
mat = [[1,**0**,**0**,3,0],
[**0**,**0**,2,3,0],
[2,**0**,**0**,2,0],
[0,1,2,3,3],]
the zeros that i marked as bold should be the answer (6)
the solution should be implemented without any loop (using recursion)
this is what i tried so far
def question_3_b(some_list,index_cord):
y = index_cord[0]
x = index_cord[1]
list_of_nums = []
def main(some_list,index_cord):
y = index_cord[0]
x = index_cord[1]
def check_right(x,y):
if x + 1 < 0:
return 0
if some_list[y][x+1] == 0:
main(some_list,(y,x+1))
else:
return 0
def check_left(x,y):
if x -1 < 0:
return 0
if some_list[y][x - 1] == 0:
main(some_list,(y, x - 1))
def check_down(x,y):
if y + 1 < 0:
return 0
try:
if some_list[y + 1][x] == 0:
main(some_list,(y + 1, x))
except:
print("out of range")
def check_up(x,y):
counter_up = 0
if y - 1 < 0:
return 0
if some_list[y - 1][x] == 0:
counter_up += 1
main(some_list,(y - 1, x))
list_of_nums.append((x,y))
right = check_right(x,y)
down = check_down(x,y)
left = check_left(x,y)
up = check_up(x, y)
main(some_list,index_cord)
print(list_of_nums)
question_3_b(mat,(0,1))
Solution #1: classic BFS
As I mention in a comment, you can tackle this problem using BFS (Breadth First Search), it will be something like this:
# This function will give the valid adjacent positions
# of a given position according the matrix size (NxM)
def valid_adj(i, j, N, M):
adjs = [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]
for a_i, a_j in adjs:
if 0 <= a_i < N and 0 <= a_j < M:
yield a_i, a_j
def biggest_zero_chunk(mat):
answer = 0
N, M = len(mat), len(mat[0])
# Mark all non zero position as visited (we are not instrested in them)
mask = [[mat[i][j] != 0 for j in range(M)] for i in range(N)]
queue = []
for i in range(N):
for j in range(M):
if mask[i][j]: # You have visited this position
continue
# Here comes the BFS
# It visits all the adjacent zeros recursively,
# count them and mark them as visited
current_ans = 1
queue = [[i,j]]
while queue:
pos_i, pos_j = queue.pop(0)
mask[pos_i][pos_j] = True
for a_i, a_j in valid_adj(pos_i, pos_j, N, M):
if mat[a_i][a_j] == 0 and not mask[a_i][a_j]:
queue.append([a_i, a_j])
current_ans += 1
answer = max(answer, current_ans)
return answer
mat = [[1,0,0,3,0],
[0,0,2,3,0],
[2,0,0,2,0],
[0,1,2,3,3],]
mat2 = [[1,0,0,3,0],
[0,0,2,3,0],
[2,0,0,0,0], # A slight modification in this row to connect two chunks
[0,1,2,3,3],]
print(biggest_zero_chunk(mat))
print(biggest_zero_chunk(mat2))
Output:
6
10
Solution #2: using only recursion (no for statements)
def count_zeros(mat, i, j, N, M):
# Base case
# Don't search zero chunks if invalid position or non zero values
if i < 0 or i >= N or j < 0 or j >= M or mat[i][j] != 0:
return 0
ans = 1 # To count the current zero we start at 1
mat[i][j] = 1 # To erase the current zero and don't count it again
ans += count_zeros(mat, i - 1, j, N, M) # Up
ans += count_zeros(mat, i + 1, j, N, M) # Down
ans += count_zeros(mat, i, j - 1, N, M) # Left
ans += count_zeros(mat, i, j + 1, N, M) # Right
return ans
def biggest_zero_chunk(mat, i = 0, j = 0, current_ans = 0):
N, M = len(mat), len(mat[0])
# Base case (last position of mat)
if i == N - 1 and j == M - 1:
return current_ans
next_j = (j + 1) % M # Move to next column, 0 if j is the last one
next_i = i + 1 if next_j == 0 else i # Move to next row if j is 0
ans = count_zeros(mat, i, j, N, M) # Count zeros from this position
current_ans = max(ans, current_ans) # Update the current answer
return biggest_zero_chunk(mat, next_i, next_j, current_ans) # Check the rest of mat
mat = [[1,0,0,3,0],
[0,0,2,3,0],
[2,0,0,2,0],
[0,1,2,3,3],]
mat2 = [[1,0,0,3,0],
[0,0,2,3,0],
[2,0,0,0,0], # A slight modification in this row to connect two chunks
[0,1,2,3,3],]
print(biggest_zero_chunk(mat.copy()))
print(biggest_zero_chunk(mat2.copy()))
Output:
6
10
Notes:
The idea behind this solution is still BFS (represented mainly in the count_zeros function). Also, if you are interested in using the matrix values after this you should call the biggest_zero_chunk with a copy of the matrix (because it is modified in the algorithm)
Description of the problem is below:
Given the matrix of M rows and N columns, 0 represents empty space,- 1 represents obstacle, and 1 represents target points (there are multiple target points).
For each empty space, if you want to reach a target point at the shortest distance, please mark the direction of the first step.
If starting up, you should mark the point as 2. if starting down , you should mark the point as 3. if starting left , you should mark the point as 4. if starting right , you should mark the point as 5.
The priority of direction is up, down, left and right from big to small, that is, if you can reach the target point with the shortest distance from a point up or down, you are supposed to start up.
Returns the matrix after marking. 0< m, n< 1000
I tried to wrote solution in python, but always get 'TypeError: 'NoneType' object is not iterable'. Really don't know why. I would really appreciate your help if you can point out the problem!
My basic idea is, for each empty cell, find its nearest target using BFS. then by specifying this empty cell as start and this nearest target as destination, i find out the direction of the first step. Code might not be so concise, thanks for your time and effort!
class Solution:
"""
#param grid: the input matrix
#return: the new matrix
"""
grid = None
def shortestPath(self, grid):
# BFS
self.grid = grid.copy()
m = len(grid)
n = len(grid[0]) if m > 0 else 0
res = [[None] * n for _ in range(m)]
for i in range(m):
for j in range(n):
if grid[i][j] not in [-1, 1]:
tarx, tary, step = self.bfs(i, j)
res[i][j] = self.search(i, j, tarx, tary)
else:
res[i][j] = grid[i][j]
return res
def search(self, i, j, tarx, tary):
dic = {0: 2, 1: 3, 2: 4, 3: 5}
dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
min_dist = float('inf')
direction = -1
for idx, d in enumerate(dirs):
x, y = i + d[0], j + d[1]
if x == tarx and y == tary:
return dic[idx]
if self.inside(x, y):
arr = [tarx, tary]
_, __, dist = self.bfs(x, y, arr)
if min_dist > dist:
min_dist = dist
direction = dic[idx]
return direction
def bfs(self, i, j, target = None):
m = len(self.grid)
n = len(self.grid[0]) if m > 0 else 0
visit = [[False] * n for _ in range(m)]
visit[i][j] = True
dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
qu = [(i, j, 0)]
while len(qu) > 0:
ti, tj, step = qu[0][0], qu[0][1], qu[0][2]
qu.pop()
for d in dirs:
x, y = ti + d[0], tj + d[1]
if self.inside(x, y) and not visit[x][y]:
if not target:
if self.grid[x][y] == 1:
return x, y, step + 1
else:
tarx, tary = target[0], target[1]
if x == tarx and y == tary:
return x, y, step + 1
visit[x][y] = True
qu.append((x, y, step + 1))
def inside(self, x, y):
if 0 <= x < len(self.grid) and 0 <= y < len(self.grid[0]) and self.grid[x][y] != -1:
return True
return False
if __name__ == '__main__':
testcase = [[1,0,0],[0,0,0]]
ans = Solution().shortestPath(testcase)
print(ans)
bfs does not return a triple from all possible execution paths. If you finish the loop without finding a solution, then you drop out the bottom and return None. This makes your unpacking assignment fail.
Please see debug for your future use; we expect you to do the initial problem diagnosis, not merely dump an untracked program on us.
I am trying to write a Python function that stores all the paths between two points on a grid. Here is the original question and I am inspired by the answers and tried to create a similar version in Python.
I was able to successfully print out all the paths found by applying a recursive function, but the problem was that when I needed them to be stored in a list, I got an list of empty list. I have tried using global variables and local variables as the list for storage, and they gave the same results. Could anyone please comment on my code below and suggest on where I did incorrectly?
Many thanks!
import numpy as np
#initialize a matrix
grid = np.arange(0, 1225).reshape((35,35))
m = 10
n = 5
#starting point
s = (0, 0)
#target point
d = (n, m)
results = []
#a function to find the next step in the path
def next_step(x, y):
step = []
dx = [0, 1]
dy = [1, 0]
for i in range(2):
next_x = x + dx[i]
next_y = y + dy[i]
if next_x >= 0 and next_x <= n and next_y >= 0 and next_y <= m:
step.append((next_x, next_y))
return step
def print_all_path(s, d, visited, path):
global results
visited[s[0]][s[1]] = True
path.append(grid[s[0]][s[1]])
if s == d:
results.append(path)
print(path)
else:
for step in next_step(s[0], s[1]):
if visited[step[0],step[1]] == False:
print_all_path(step, d, visited, path)
path.pop()
visited[s[0]][s[1]] = False
def print_path(s, d):
visited = np.zeros((35,35), dtype = bool)
path = []
print_all_path(s, d, visited, path)
print_path(s, d)
The problem is probably that when you append, you only append path I suspect you did something like this:
# global
all_lists = []
# your functions
...
def print_all_path(s, d, visited, path):
global results
visited[s[0]][s[1]] = True
path.append(grid[s[0]][s[1]])
if s == d:
results.append(path)
print(path)
all_lists.append(path)
...
However, path is still linked to the original path variable.
You can solve this by using:
all_lists.append(path + [])
This copies the list and removes the link
So the overall program is now
import numpy as np
#initialize a matrix
grid = np.arange(0, 1225).reshape((35,35))
m = 10
n = 5
#starting point
s = (0, 0)
#target point
d = (n, m)
results = []
all_paths = []
#a function to find the next step in the path
def next_step(x, y):
step = []
dx = [0, 1]
dy = [1, 0]
for i in range(2):
next_x = x + dx[i]
next_y = y + dy[i]
if next_x >= 0 and next_x <= n and next_y >= 0 and next_y <= m:
step.append((next_x, next_y))
return step
def print_all_path(s, d, visited, path):
global results
visited[s[0]][s[1]] = True
path.append(grid[s[0]][s[1]])
if s == d:
results.append(path)
all_paths.append(path + [])
#print(path)
else:
for step in next_step(s[0], s[1]):
if visited[step[0],step[1]] == False:
print_all_path(step, d, visited, path)
path.pop()
visited[s[0]][s[1]] = False
def print_path(s, d):
visited = np.zeros((35,35), dtype = bool)
path = []
print_all_path(s, d, visited, path)
print_path(s, d)
print all_paths
I would like to write a function to process a list of integers, best way is to show as an example:
input [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3] will return [6,-6,6,-6]
I have a draft here that will actually work:
def group_pos_neg_list(nums):
p_nums = []
# to determine if the first element >=0 or <0
# create pos_combined and neg_combined as a list to check the length in the future
if nums[0] >= 0:
pos_combined, neg_combined = [nums[0]], []
elif nums[0] < 0:
pos_combined, neg_combined = [], [nums[0]]
# loop over each element from position 1 to the end
# accumulate pos num and neg nums and set back to 0 if next element is different
index = 1
while index < len(nums):
if nums[index] >= 0 and nums[index-1] >= 0: # both posivite
pos_combined.append(nums[index])
index += 1
elif nums[index] < 0 and nums[index-1] < 0: # both negative
neg_combined.append(nums[index])
index += 1
else:
if len(pos_combined) > 0:
p_nums.append(sum(pos_combined))
pos_combined, neg_combined = [], [nums[index]]
elif len(neg_combined) > 0:
p_nums.append(sum(neg_combined))
pos_combined, neg_combined = [nums[index]], []
index += 1
# finish the last combined group
if len(pos_combined) > 0:
p_nums.append(sum(pos_combined))
elif len(neg_combined) > 0:
p_nums.append(sum(neg_combined))
return p_nums
But I am not quite happy with it, because it looks a bit complicate.
Especially that there is a repeating part of code:
if len(pos_combined) > 0:
p_nums.append(sum(pos_combined))
pos_combined, neg_combined = [], [nums[index]]
elif len(neg_combined) > 0:
p_nums.append(sum(neg_combined))
pos_combined, neg_combined = [nums[index]], []
I have to write this twice as the final group of integers will not be counted in the loop, so an extra step is needed.
Is there anyway to simplify this?
Using groupby
No need to make it that complex: we can first groupby the signum, and then we can calculate the sum, so:
from itertools import groupby
[sum(g) for _, g in groupby(data, lambda x: x >= 0)]
This then produces:
>>> from itertools import groupby
>>> data = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
>>> [sum(g) for _, g in groupby(data, lambda x: x >= 0)]
[6, -6, 6, -6]
So groupby produces tuples with the "key" (the part we calculate with the lambda), and an iterable of the "burst" (a continuous subsequence of elements with the same key). We are only interested in the latter g, and then calculate sum(g) and add that to the list.
Custom made algorithm
We can also write our own version, by using:
swap_idx = [0]
swap_idx += [i+1 for i, (v1, v2) in enumerate(zip(data, data[1:]))
if (v1 >= 0) != (v2 >= 0)]
swap_idx.append(None)
our_sums = [sum(data[i:j]) for i, j in zip(swap_idx, swap_idx[1:])]
Here we first construct a list swap_idx that stores the indices where of the element where the signum changes. So for your sample code that is:
>>> swap_idx
[0, 4, 7, 11, None]
The 0 and None are added by the code explicitly. So now that we identified the points where the sign has changed, we can sum these subsequences together, with sum(data[i:j]). We thus use zip(swap_idx, swap_idx[1:]) to obtain two consecutive indices, and thus we can then sum that slice together.
More verbose version
The above is not very readable: yes it works, but it requires some reasoning. We can also produce a more verbose version, and make it even more generic, for example:
def groupby_aggregate(iterable, key=lambda x: x, aggregate=list):
itr = iter(iterable)
nx = next(itr)
kx = kxcur = key(nx)
current = [nx]
try:
while True:
nx = next(itr)
kx = key(nx)
if kx != kxcur:
yield aggregate(current)
current = [nx]
kxcur = kx
else:
current.append(nx)
except StopIteration:
yield aggregate(current)
We can then use it like:
list(groupby_aggregate(data, lambda x: x >= 0, sum))
You can use itertools.groupby, utilizing a key to group by all the values greater than or equal to zero:
import itertools
s = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
new_s = [sum(b) for a, b in itertools.groupby(s, key=lambda x: x >=0)]
Output:
[6, -6, 6, -6]
Here is a way to do without any external imports, only using reduce():
def same_sign(a, b):
"""Returns True if a and b have the same sign"""
return (a*b>0) or (a>=0 and b>=0)
l = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
reduce(
lambda x, y: (x+y if same_sign(x,y) else [x, y]) if not isinstance(x, list) else x[:-1] + [x[-1] + y] if same_sign(x[-1],y) else x + [y],
l
)
#[6, -6, 6, -6]
Explanation
This is a bit hard to explain, but I'll try.
From the docs calling reduce() will:
Apply function of two arguments cumulatively to the items of iterable, from left to right
In this case I take two values (x and y) from your list and do the following:
If x is not a list:
If x and y have the same sign (product >=0), sum them
Otherwise return a list [x, y]
If x is a list, only modify the last element of x.
If the signs match, add y.
Otherwise append a new element to the list x
Note
You probably shouldn't do it this way because the code is hard to read and understand. I just wanted to show that it was possible.
Update
A more readable version of the same code above:
def same_sign(a, b):
"""Returns True if a and b have the same sign"""
return (a*b>0) or (a>=0 and b>=0)
l = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
def reducer(x, y):
if isinstance(x, list):
if same_sign(x[-1], y):
return x[:-1] + [x[-1] + y]
else:
return x + [y]
else:
if same_sign(x, y):
return x+y
else:
return [x, y]
reduce(reducer, l)
#[6, -6, 6, -6]
Hi I am trying to convert my iterative algorithm to recursive solution to achieve Dynamic Programming after it's done (Do suggest me other ways to reduce time complexity of this triple nested iteration). I am not good with recursion. I had tried to convert it but it is giving me index out of range errors.
Iterative Approach:
def foo(L):
L=sorted(L)
A = 0
for i,x in enumerate(L):
for j,y in enumerate(L):
if x != y:
if (y%x ==0):
for k,z in enumerate(L):
if y != z:
if (z%y==0) and (y%x==0):
A= A+1
return A
Recursive Approach:
A =i =j= k =0 #Initializing globals
def foo(L):
L=sorted(L)
global A ,i,j,k
x=y=z=L
luckyOne(x,y,z)
return A
def luckyOne(x,y,z):
global i,j,k
while(i< len(x) and j < len(y) and k < len(z)):
while(x[i] != y[j]):
luckyTwo(x[i:],y[j:],z[k:])
i+=1
luckyOne(x[i:],y[j:],z[k:])
# i+=1
# return A
i+=1
luckyOne(x[i:],y[j:],z[k:])
return 0
def luckyTwo(x,y,z):
global i,j,k
while (i< len(x) and j < len(y) and k < len(z)):
while(y[j]%x[i]==0):
luckyThree(x[i:],y[j:],z[k:])
j+=1
luckyTwo(x[i:],y[j:],z[k:])
j+=1
luckyTwo(x[i:],y[j:],z[k:])
return 0
def luckyThree(x,y,z):
global A ,i,j,k
while (i< len(x) and j < len(y) and k < len(z)):
while (y[j]!=z[k]):
while((z[k]%y[j]==0) and (y[j]%x[i]==0)):
A+=1
print 'idr aya'
k+=1
luckyThree(x[i:],y[j:],z[k:])
k+=1
luckyThree(x[i:],y[j:],z[k:])
return 0
The input should be like L=['1','2','3']
This is the fastest version I can come up with:
def foo(lst):
edges = {x: [y for y in lst if x != y and y % x == 0] for x in set(lst)}
return sum(len(edges[y]) for x in lst for y in edges[x])
This should be significantly faster (1/7th the time in my test of lists with 100 elements).
The algorithm is essentially to build a directed graph where the nodes are the numbers in the list. Edges go from node A to node B iff the integer values of those nodes are different and A divides evenly into B.
Then traverse the graph. For each starting node A, find all the nodes B where there's an edge from A to B. On paper, we would then go to all the next nodes C, but we don't need to... we can just count how many edges are leaving node B and add that to our total.
EDIT
Depending on the distribution of values in the list, this is probably faster:
def foo(lst):
counts = Counter(lst)
edges = {x: [y for y in counts if x != y and y % x == 0] for x in counts}
return sum(counts[x] * counts[y] * sum(counts[z] for z in edges[y]) for x in counts for y in edges[x])
Here, you can think of nodes as having a numeric value and a count. This avoids duplicate nodes for duplicate values in the input. Then we basically do the same thing but multiply by the appropriate count at each step.
EDIT 2
def foo(lst):
counts = collections.Counter(lst)
edges = collections.defaultdict(list)
for x, y in itertools.combinations(sorted(counts), 2):
if y % x == 0:
edges[x].append(y)
return sum(counts[x] * counts[y] * sum(counts[z] for z in edges[y]) for x in counts for y in edges[x])
Slight improvement thanks to #Blckknght. Sorting the unique values first saves some time in enumeration.
EDIT 3
See comments, but the original code in this question was actually wrong. Here's code that (I think) does the right thing based on the problem description which can be found in the comments:
def foo3(lst):
count = 0
for x, y, z in itertools.combinations(lst, 3):
if y % x == 0 and z % y == 0:
count += 1
return count
print(foo3([1, 2, 3, 4, 5, 6])) # 3
print(foo3([6, 5, 4, 3, 2, 1])) # 0
EDIT 4
Much faster version of the previous code:
def foo4(lst):
edges = [[] for _ in range(len(lst))]
for i, j in itertools.combinations(range(len(lst)), 2):
if lst[j] % lst[i] == 0:
edges[i].append(j)
return sum(len(edges[j]) for i in range(len(lst)) for j in edges[i])
EDIT 5
More compact version (seems to run in about the same amount of time):
def foo5(lst):
edges = [[j for j in range(i + 1, len(lst)) if lst[j] % lst[i] == 0] for i in range(len(lst))]
return sum(len(edges[j]) for i in range(len(lst)) for j in edges[i])
Here's how I'd solve your problem. It should use O(N**2) time.
def count_triple_multiples(lst):
count = collections.Counter(lst)
double_count = collections.defaultdict(int)
for x, y in itertools.combinations(sorted(count), 2):
if y % x == 0:
double_count[y] += count[x] * count[y]
triple_count = 0
for x, y in itertools.combinations(sorted(double_count), 2):
if y % x == 0:
triple_count += double_count[x] * count[y]
return triple_count
My algorithm is very similar to the one smarx is using in his answer, but I keep a count of the number of edges incident to a given value rather than a list.
As far as speeding things up goes (instead of going recursive), testing with 1000 entries, slicing the sorted list at each level cut time by more than half for me (gets rid of numbers less than y, z at their respective levels:
def foo(L):
assert 0 not in L
L=sorted(L)
A = 0
for i,x in enumerate(L):
for j,y in enumerate(L[i + 1:]):
if x != y and not y % x:
for k,z in enumerate(L[i + j + 2:]):
if y != z and not z % y:
A = A + 1
return A