Move Function not Working for N-Puzzle Problem - python

I'm trying to make a IDDFS algorithm for a n-puzzle problem. However, the move function doesn't work properly. It will output the next move but change the values of the previous one (argument). Any advice on how to prevent this from happening would be greatly appreciated :)
def move_blank(i, j, n):
if i + 1 < n:
yield i + 1, j
elif i - 1 >= 0:
yield i - 1, j
elif j + 1 < n:
yield i, j + 1
elif j - 1 >= 0:
yield i, j - 1
def move(state):
[i, j, grid] = state
n = len(grid)
for pos in move_blank(i, j, n):
i1, j1 = pos
grid[i][j], grid[i1][j1] = grid[i1][j1], grid[i][j]
yield [i1, j1, grid]
grid[i][j], grid[i1][j1] = grid[i1][j1], grid[i][j]
def is_goal(state, goal):
return state == goal
def dfs_rec(puz, goal):
if is_goal(puz[-1], goal):
return puz
else:
for next_state in move(puz[-1]):
if next_state not in puz:
next_path = puz + [next_state]
solution = dfs_rec(next_path, goal)
if solution != None:
return solution
return None
goal = [0, 2, [[3, 2, 0], [6, 1, 8], [4, 7, 5]]]
test = [0, 0, [[0, 7, 1], [4, 3, 2], [8, 6, 5]]]
path = dfs_rec([test], goal)

The problem is in move_blank. The generated moves are not mutually exclusive, yet only one of them will be generated. Replace all elifs with a simple if:
def move_blank(i, j, n):
if i + 1 < n:
yield i + 1, j
if i - 1 >= 0:
yield i - 1, j
if j + 1 < n:
yield i, j + 1
if j - 1 >= 0:
yield i, j - 1
There are other, less critical, issues with implementation:
if next_state not in puz is awfully expensive. Enumerating states and storing their id in a set would be much better (there are 362k states for an 8-puzzle, list lookup is faster than set only up to ~30 elements)
reliance on mutable arguments isn't a good practice. No immediate issues here but it might bite when you don't expect.
Storing state as a 9-tuple would fix this and the previous concern.
solution = dfs_rec(next_path, goal) This problem is solvable and would be much cheaper without recursion. Yes, wikipedia pseudocode is not optimal.
it is not IDDFS - there is no depth limit per iteration

Related

recursive finding longest path of same element in 2d list

I need to find the longest path of 0's in a 2d matrix recursively and can't figure out how to do it.( from a given (i , j) it can only move up, down, right or left)
For example this matrix:
mat = [[1, 0, 0, 3, 0],
[0, 0, 2, 3, 0],
[2, 0, 0, 2, 0],
[0, 1, 2, 3, 3]]
print(question3_b(mat))
This should return 6 as there are communities of sizes 1,3,6.
My attempt: I created a few wrapper functions to find the maximum in a list, and a function to find the route at a given (i,j) element and add it to a list, and doing this on every point(i,j) in the matrix.
def question3_b(mat) -> int:
rows: int = len(mat)
cols: int = len(mat[0])
community_lengths = list()
for i in range(rows):
for j in range(cols):
visited = zeros(rows, cols) # create a 2d list of 0's with size rows,cols
a = findcommunity(mat, (i, j), visited)
print("a=", a)
community_lengths.append(a)
print(community_lengths)
return findmax(community_lengths)
def findcommunity(mat: list, indices: tuple, visited: list): # find length of community
#global rec_counter
#rec_counter += 1
i, j = indices
rows = len(mat)
cols = len(mat[0])
if mat[i][j] == 0:
visited[i][j] = 1
if i < rows - 1:
if visited[i + 1][j] == 0:
return 1 + findcommunity(mat, (i + 1, j), visited)
if j < cols - 1:
if visited[i][j + 1] == 0:
return 1 + findcommunity(mat, (i, j + 1), visited)
if i > 0:
if visited[i - 1][j] == 0:
return 1 + findcommunity(mat, (i - 1, j), visited)
if j > 0:
if visited[i][j - 1] == 0:
return 1 + findcommunity(mat, (i, j - 1), visited)
else: # mat[i][j]!=0
return 0
def zeros(rows:int,cols:int)->list: #create a 2d matrix of zeros with size (rows,cols)
if rows==1 and cols==1:return [[0]]
if rows==1 and cols>1:return [[0]*cols]
if rows>1 and cols>1:return zeros(rows-1,cols)+zeros(1,cols)
def findmax(arr:list)->int: # find max in an array using recursion
if len(arr)==2:
if arr[0]>arr[1]:return arr[0]
else:return arr[1]
else:
if arr[0]>arr[1]:
arr[1]=arr[0]
return findmax(arr[1:])
else:
return findmax(arr[1:])
Where did I go wrong? for a matrix of 4X4 zeros, I expect it to run 16*16 times[16 times for each i,j, and there are 16 elements in the matrix]. but it only runs once.
zeros is a recursive function I made that functions like np.zeros, it creates a 2d matrix full of 0's with specified size.
It got really messy but I tried to just change your code instead of writing a new solution. You should have a look at collections deque. Saw this several times where people keep track of visited a lot easier.
I changed visited to outside of the loop and defined it with np.zeros (didn't have your function ;) ). I'm not sure if your recursive function calls at return were wrong but your if-statements were, or at least the logic behind it (or I didn't understand, also possible :) )
I changed that block completly. The first time you come across a 0 in mat the recursive part will dig into the mat as long as it finds another 0 left,right,bottom or top of it (that's the functionality behind dc and dr). That's where the community_counter is increasing. If the function is returning the last time and you jump out to the outerloop in question_3b the counter gets resetted and searchs for the next 0 (next start of another community).
import numpy as np
def question3_b(mat) -> int:
rows: int = len(mat)
cols: int = len(mat[0])
community_lengths = list()
visited = np.zeros((rows, cols)) # create a 2d list of 0's with size rows,cols
community_count = 0
for row in range(rows):
for col in range(cols):
if visited[row][col]==0:
community_count,visited = findcommunity(mat, (row, col), visited, community_count)
if community_count!=0:
community_lengths.append(community_count)
community_count=0
return community_lengths
def findcommunity(mat: list, indices: tuple, visited: list,community_count: int): # find length of community
i, j = indices
rows = len(mat)
cols = len(mat[0])
visited[i][j] = 1
if mat[i][j] == 0:
community_count += 1
dr = [-1, 0, 1, 0]
dc = [0,-1, 0, 1]
for k in range(4):
rr = i + dr[k]
cc = j + dc[k]
if 0<=rr<rows and 0<=cc<cols:
if visited[rr][cc]==0 and mat[rr][cc]==0:
community_count, visited = findcommunity(mat, (rr,cc), visited, community_count)
return community_count,visited
else:
return community_count,visited
mat = [[1, 0, 0, 3, 0],
[0, 0, 2, 3, 0],
[2, 0, 0, 2, 0],
[0, 1, 2, 3, 3]]
all_communities = question3_b(mat)
print(all_communities)
# [6, 3, 1]
print(max(all_communities))
# 6
EDIT
Here is the findcommunity function in your way. Tested it and it works aswell.
def findcommunity(mat: list, indices: tuple, visited: list,community_count: int): # find length of community
i, j = indices
rows = len(mat)
cols = len(mat[0])
visited[i][j] = 1
if mat[i][j] == 0:
community_count += 1
if i < rows - 1:
if visited[i + 1][j] == 0:
community_count, visited = findcommunity(mat, (i + 1, j), visited, community_count)
if j < cols - 1:
if visited[i][j + 1] == 0:
community_count, visited = findcommunity(mat, (i, j + 1), visited, community_count)
if i > 0:
if visited[i - 1][j] == 0:
community_count, visited = findcommunity(mat, (i - 1, j), visited, community_count)
if j > 0:
if visited[i][j - 1] == 0:
community_count, visited = findcommunity(mat, (i, j - 1), visited, community_count)
return community_count,visited
else:
return community_count,visited
Here a completely different approach, in case someone is interested.
import numpy as np
mat = [[1, 0, 0, 3, 0],
[0, 0, 2, 3, 0],
[2, 0, 0, 2, 0],
[0, 1, 2, 3, 3]]
mat = np.array(mat)
# some padding of -1 to prevent index error
mask = np.full(np.array(mat.shape) + 2, -1)
mask[1:-1, 1:-1 ] = mat
# visiteds are set to -2
def trace(f, pt):
mask[tuple(pt)], pts = -2, [pt - 1]
pts += [trace(f, pt + d) for d in
([0, 1], [1, 0], [0, -1], [-1, 0]) if
mask[tuple(pt + d)] == f]
return pts
clusters = lambda f: {tuple(pt-1): trace(f, pt) for pt in np.argwhere(mask==f) if mask[tuple(pt)]==f}
# now call with value your looking for
print(clusters(0))

How to optimize working (but slow) stair permutation function?

The problem is, given a number of blocks, how many ways are there to build stairs using that finite amount of blocks where there is always any incline between any two neighboring steps.
This means that a two step staircase from 100 to 1 step is valid. Of course, more blocks mean you can have more steps.
I wrote a function that accomplishes this, albeit very slowly when it gets to larger number of blocks, and I'm not sure how I can improve its runtime.
If you want a quick breakdown of my logic, it works out logically that by recursively expanding the highest step into all possible permutations of two steps (that would still put the second step above the former second step), eventually you get all possible step permutations.
Maybe there's a more mathy way of doing this, but I approached it from a programming pov. Welcome to hear any different suggestions though, if my approach is just too slow!
def solution(n):
cases = 0
q = [[x, n - x] for x in range(n) if x > n - x and n - x > 0]
while q:
curr = q.pop(0)
cases += 1
q += [[x, curr[0] - x, *curr[1:]] for x in range(curr[1], curr[0] - curr[1]) if x > curr[0] - x > curr[1]]
return cases
output, to show it works
>>> solution(15)
[8, 7]
[9, 6]
[10, 5]
[11, 4]
[12, 3]
[13, 2]
[14, 1]
[6, 5, 4]
[7, 5, 3]
[8, 4, 3]
[7, 6, 2]
[8, 5, 2]
[9, 4, 2]
[10, 3, 2]
[8, 6, 1]
[9, 5, 1]
[10, 4, 1]
[11, 3, 1]
[12, 2, 1]
[6, 4, 3, 2]
[6, 5, 3, 1]
[7, 4, 3, 1]
[7, 5, 2, 1]
[8, 4, 2, 1]
[9, 3, 2, 1]
[5, 4, 3, 2, 1]
26
Here is an alternative recursive/backtracking approach:
def solve_recursive(n):
solutions = []
def f(sol, i, n):
if n == 0 and len(sol) >= 2:
solutions.append(sol)
for j in range(i+1, n+1):
sol.append(j)
f(sol, j, n-j)
sol.pop()
f([], 0, n)
return len(solutions)
It is a bit more efficient than your version, at n=105 this takes 3.3s on my computer, compared to 13.4s in the version you posted.
The idea is to recursively fill the buckets using higher and higher values, so that the requirement is met.
If we are only interested in the count, and not the paths, we can get faster by omitting the path bookkeeping:
from functools import lru_cache
def solution_faster(n):
#lru_cache(maxsize=None)
def f(i, cnt, n):
if n == 0 and cnt >= 2:
return 1
ans = 0
for j in range(i+1, n+1):
ans += f(j, cnt+1, n-j)
return ans
return f(0, 0, n)
This takes 0.04s for n=105 on my computer. But we can do even better by removing the cnt as well!
def solution_even_faster(n):
#lru_cache(maxsize=None)
def f(i, n):
if n == 0:
return 1
ans = 0
for j in range(i+1, n+1):
ans += f(j, n-j)
return ans
ans = 0
for j in range(1, n//2 + 1):
ans += f(j, n-j)
return ans
Now we have O(N^3) (pseudo-polynomial) time complexity. This takes 0.008s in my computer.
O(N^2) solutions are possible with dynamic programming approaches as well. I suggest checking out this reference: https://www.geeksforgeeks.org/count-of-subsets-with-sum-equal-to-x/
Let dp[i][j] denote number of ways you can get j blocks using the first i steps.
In 0th row, only dp[0][0] will be 1 and everything else will be 0 because initially with 0 steps you can get 0 block in one way.
For other rows, dp[i][j] = dp[i - 1][j] + dp[i - 1][j - i] because dp[i - 1][j] was old number of ways to get j blocks and after using block of size i then dp[i - 1][j - i] will also contribute to dp[i][j].
This uses O(n ^ 2) space complexity. But you can reduce it to O(n) by observing that current row depends on previous row only. So this reduces space to O(n). But time complexity remains the same which is O(n ^ 2).
def solution(n):
# we can reach 0 in 1 way which using no blocks
prev = [0 for _ in range(n + 1)]
prev[0] = 1
# start from n - 1 block and go up to 1
for i in range(n - 1, 0, -1):
curr = list(prev)
for j in range(i, n + 1):
curr[j] = curr[j] + prev[j - i]
prev = list(curr)
return prev[-1]
Here prev denotes dp[i-1] and curr denotes dp[i]

Coding Practice: Why do do the iterators need to be aliased?

I've completed a question from algo expert that asks you to loop through an array of numbers to find the two numbers that add to a target sum. I'm trying to understand why this function does not work without the iterators being aliased.
input: [3, 5, -4, 8, 11, 1, -1, 6], 10
output: [-1, 11]
This is the correct solution:
def twoNumberSum(array, targetSum):
for i in range(len(array) - 1):
firstNum = array[i]
for j in range(i + 1, len(array)):
secondNum = array[j]
if firstNum + secondNum == targetSum:
return [firstNum, secondNum]
return []
and I don't understand why this does not work:
def twoNumberSum(array, targetSum):
for i in range(len(array) - 1):
for j in range(i + 1, len(array)):
if i + j == targetSum:
return [i, j]
return []
As mentioned in the comments, i and j are no values but your index.
It is always good to just test the output with a small example.
when
a=[6,7,8,9,10]
try to see what the range is over the length of a
for i in range(len(a)):
print(i)
this will result in
0
1
2
3
4
and not
6
7
8
9
10
I hope that made it clearer.
Keep healthy.

Implementing Insertion Sort in Python with For Loops only

I tried implementing Insertion sort with for loops only and wrote the following code:
def isort(L): #implementation with a for loop
for i in range(1,len(L)):
small = L[i]
M = range(i)
M.reverse()
for j in M:
if small<L[j]:
L[j+1]=L[j]
else:
break
L[j+1] = small
return L
L = [5,4,3,2,1]
M = isort(L)
print M
This gives the output [5,1,2,3,4]. Can someone please point out where I am making a mistake
Change (the fix shown in the question is easy, the one-off error was caused by one little +1 :)):
L[j+1] = small
To:
L[j] = small
Testing:
>>> isort([5, 4, 3, 2, 1])
[1, 2, 3, 4, 5]
However, there are some other things with your code, as illustrated- it will not work alot of the time. With a fair few tweaks, we can get it to work:
def isort(L):
for i in range(1,len(L)):
small = L[i]
M = range(-1, i)
M.reverse()
for j in M:
if j>=0 and small<L[j]:
L[j+1]=L[j]
else:
break
L[j+1] = small
return L
Testing:
>>> isort([4, 5, 3, 2, 1])
[1, 2, 3, 4, 5]
The post condition for the inner loop is that j is pointing for the first value that is smaller than small (this is achieved by the break call). However, the loop naturally exists when j=0, therefore in every last inner iteration, the condition is not what you'd expect.
To fix it, I suggest initializing M from -1:
M = range(-1, i)
But then, you have to check as well that j is positive (to avoid making changes you don't want to):
if j>=0 and small<L[j]:
L[j+1]=L[j]
This is little tricky :
I took the inner loop range function as range(j, -2, -1) , so the inner loop always breaks at one position ahead, so the last statement arr[j + 1] = key works perfectly
arr = [5, 4, 3, 2, 1]
for i in range(1, len(arr)):
j = i - 1
key = arr[i]
for j in range(j, -2, -1):
if j < 0 or key >= arr[j]:
break
else:
arr[j + 1] = arr[j]
arr[j + 1] = key
if __name__ == "__main__":
n = int(input("How many numbers ?\t"))
nums = [int(x) for x in input("Enter {} numbers\t".format(n)).split()]
for i in range(1,n):
val = nums[i]
for j in range(i-1,-2,-1):
if j < 0 : break
if nums[j] > val:
nums[j+1] = nums[j]
else:
break
nums[j+1] = val
for num in nums:
print(num,end=' ')
print()

Merge sort implementation in python giving incorrect result

I am trying to implement the merge sort algorithm described in these notes by Jeff Erickson on page 3. but even though the algorithm is correct and my implementation seems correct, I am getting the input list as output without any change. Can someone point out the anomalies, if any, in it.
def merge(appnd_lst, m):
result = []
n = len(appnd_lst)
i, j = 0, m
for k in range(0, n):
if j < n:
result.append(appnd_lst[i])
i += 1
elif i > m:
result.append(appnd_lst[j])
j += 1
elif appnd_lst[i] < appnd_lst[j]:
result.append(appnd_lst[i])
i += 1
else:
result.append(appnd_lst[j])
j += 1
return result
def mergesort(lst):
n = len(lst)
if n > 1:
m = int(n / 2)
left = mergesort(lst[:m])
right = mergesort(lst[m:])
appnd_lst = left
appnd_lst.extend(right)
return merge(appnd_lst, m)
else:
return lst
if __name__ == "__main__":
print mergesort([3, 4, 8, 0, 6, 7, 4, 2, 1, 9, 4, 5])
There are three errors in your merge function a couple of indexing errors and using the wrong comparison operator. Remember python list indices go from 0 .. len(list)-1.
* ...
6 if j > n-1: # operator wrong and off by 1
* ...
9 elif i > m-1: # off by 1
* ...

Categories