OutputError: Find the longest path in a matrix with given constraints - python

My matrix is:
4 8 7 3
2 5 9 3
6 3 2 5
4 4 1 6
Problem (Skiing):
Each number represents the elevation of that area of the mountain.
From each area (i.e. box) in the grid, you can go north, south, east, west - but only if the elevation of the area you are going into is less than the one you are in.
I.e. you can only ski downhill.
You can start anywhere on the map and you are looking for a starting point with the longest possible path down as measured by the number of boxes you visit.
And if there are several paths down of the same length, you want to take the one with the steepest vertical drop, i.e. the largest difference between your starting elevation and your ending elevation.
My Solution:
def findSkiPath():
mySolution = [0] * 3
mySolution[0] = 0 # Distance
mySolution[1] = 0 # Drop
cellIndexRow = 0
cellIndexCol = 0
myPath = []
myMatrix = [[4, 5, 8, 7],[1, 1, 5, 9], [0, 7, 5, 5], [7, 4, 2, 9]]
countRows = len(myMatrix)
countCols = len(myMatrix[0])
for i in range(0, countRows - 1):
for j in range(0, countCols - 1):
myValue = myMatrix[i][j]
myPath.append(myValue)
#check east
cellIndexRow = i
cellIndexCol = j + 1
checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )
#check west
cellIndexRow = i
cellIndexCol = j - 1
checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )
#check north
cellIndexRow = i - 1
cellIndexCol = j
checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )
#check south
cellIndexRow = i + 1
cellIndexCol = j
checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )
print (mySolution)
def checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath ):
#The base case - If we go beyond the limits of the matrix
if (cellIndexRow < 0 or cellIndexRow > (len(myMatrix) - 1) or cellIndexCol < 0 or cellIndexCol > (len(myMatrix[0]) - 1)):
evaluateSolution(mySolution , myPath )
return
#check if the next cell has a lower value than the current cell
tmpValue = myMatrix[cellIndexRow][cellIndexCol]
if tmpValue < myValue:
newPath = myPath
newPath.append(tmpValue)
r = cellIndexRow
c = cellIndexCol
#check east
cellIndexRow = r
cellIndexCol = c + 1
checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )
#check west
cellIndexRow = r
cellIndexCol = c - 1
checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )
#check north
cellIndexRow = r - 1
cellIndexCol = c
checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )
#check south
cellIndexRow = r + 1
cellIndexCol = c
checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )
evaluateSolution(mySolution , myPath )
def evaluateSolution(mySolution , myPath ):
myDistance = 1
mySolutionDistance = int(mySolution[0])
mySolutionDrop = int(mySolution[1])
if myDistance < mySolutionDistance:
return
myDrop = myPath[0] - myPath[-1]
if myDistance > mySolutionDistance or myDrop > mySolutionDrop:
mySolution[0] = myDistance
mySolution[1] = mySolutionDrop
mySolution[2] = myPath
if __name__ == "__main__":
findSkiPath()
Issues:
Current output (distance, drop, path):
[1, 0, [4, 2, 8, 7, 3, 4, 2, 5, 2, 3, 2, 1, 7, 3, 2, 5, 2, 3, 2, 1, 9,
3, 5, 2, 3, 2, 1, 7, 3, 2, 1, 6, 3, 2, 1, 2, 4, 3, 2, 1, 2, 1]]
Expected output:
[5,8,[9,5,3,2,1]]
On this particular map, the longest path down is of length=5, drop=8 (9-1=8), and path: 9-5-3-2-1.

One can approach the in the question described challenge in two different ways:
using a recursive algorithm which walks only valid paths checking already while iterating through the matrix elements if the given requirements are fulfilled
doing it in two steps:
2.1. getting an iterator over all possible paths with a simple call to an in the itertools module available function permutations()
2.2. picking from the generated paths these ones which fulfill the requirements
The code for the second approach is easier to write and to understand, but the huge amount of possible paths already for a 4x4 size of a matrix makes it practically impossible to run it for larger matrix sizes.
The code of the first approach can handle larger sizes of matrices, but has the disadvantage of being harder to grasp how it works to adjust it in case of other constraints.
The question asked here is a 100% 1:1 duplicate of a question asked two years ago here on stackoverflow and titled "Maximum number of elements in the path of a matrix". Anyway here the solution given in an answer to that old question again:
theMatrix = [
[ 4, 8, 7, 3],
[ 2, 5, 9, 3],
[ 6, 3, 2, 5],
[ 4, 4, 1, 6]
]
def longest_path(matrix):
def inner_longest_path(x, y):
best, best_path = 0, []
# for all possible neighbor cells...
for dx, dy in ((+1, 0), (-1, 0), (0, +1), (0, -1)):
# if cell is valid and strictly smaller...
if (0 <= x + dx < len(matrix) and 0 <= y + dy < len(matrix[x])
and matrix[x+dx][y+dy] < matrix[x][y]):
n, path = inner_longest_path(x+dx, y+dy) ### RECURSION
# check if the path starting at that cell is better
if n > best:
best, best_path = n, path
return best + 1, [matrix[x][y]] + best_path
return max(inner_longest_path(x, y) for x, row in enumerate(matrix)
for y, _ in enumerate(row))
print( longest_path(theMatrix) )
The code above prints:
(5, [9, 5, 3, 2, 1])
Now let's take a look also at the code of the non-recursive approach provided here be me myself:
# myMatrix = [[4, 5, 8, 7],[1, 1, 5, 9], [0, 7, 5, 5], [7, 4, 2, 9]]
# 4 5 8 7
# 1 1 5 9
# 0 7 5 5
# 7 4 2 9
myMatrix = [[4, 5, 8],[1, 1, 5], [0, 7, 5]]
# 4 5 8
# 1 1 5
# 0 7 5
# myMatrix = [[4, 5],[1, 1]]
# 4 5
# 1 1
def getAllValidSkiingPathsFrom(myMatrix):
# def getDictRepresentationOf(myMatrix):
dctOfMatrix = {}
for row in range(len(myMatrix)):
for column in range(len(myMatrix[0])):
currPoint = (column, row)
dctOfMatrix[currPoint] = myMatrix[row][column]
lstIndicesOfAllMatrixPoints = list(dctOfMatrix.keys())
setAllPossiblePaths = set()
from itertools import permutations
for pathCandidate in permutations(lstIndicesOfAllMatrixPoints):
lstPossiblePath = []
prevIndexTuple = pathCandidate[0]
lstPossiblePath.append(prevIndexTuple)
for currIndexTuple in pathCandidate[1:]:
if abs(currIndexTuple[0]-prevIndexTuple[0]) + abs(currIndexTuple[1]-prevIndexTuple[1]) > 1:
break # current path indices not allowed in path (no diagonals or jumps)
else:
if dctOfMatrix[currIndexTuple] >= dctOfMatrix[prevIndexTuple]:
break # only "down" is allowed for "skiing"
else:
lstPossiblePath.append(currIndexTuple)
prevIndexTuple = currIndexTuple
if len(lstPossiblePath) > 1 and tuple(lstPossiblePath) not in setAllPossiblePaths:
setAllPossiblePaths.add(tuple(lstPossiblePath))
return setAllPossiblePaths, dctOfMatrix
#:def getAllValidSkiingPathsFrom
setAllPossiblePaths, dctOfMatrix = getAllValidSkiingPathsFrom(myMatrix)
for path in setAllPossiblePaths:
for point in path:
print(dctOfMatrix[point], end=',')
Here the results for the 2x2 and 3x3 versions of myMatrix:
# 4 5
# 1 1
4,1,
5,1,
5,4,
5,4,1,
# 4 5 8
# 1 1 5
# 0 7 5
5,1,
8,5,1,
7,1,
4,1,
5,1,
5,4,
8,5,1,
1,0,
5,4,1,0,
8,5,4,1,
8,5,4,1,0,
8,5,4,
8,5,
7,0,
7,5,
8,5,
4,1,0,
5,4,1,
I hope the code is self-explanatory, but if not here the rough idea:
build a dictionary representing the matrix where the keys are tuples of (column,row)"coordinates" and the values the values in the matrix.
build a list of all possible full paths within the matrix (permutations)
filter the list of all possible full paths to extract only the valid ones according to what is required (apply criteria).
I didn't have run the computational very expensive calculation of the result for the 4x4 matrix as it takes for sure more than several minutes on my box.
For the sake of completeness I would like to mention that there is also another question HERE on stackoverflow which is a variation of THIS question (it has a bit other rules for valid paths and requests an algorithm able to work on irregular matrices).

The main problem with this is that you don't do any sort of backtracking. You are traversing the matrix appropriately, but you don't do anything to maintain the concept of a particular path. Instead, for each square you visit, you simply append all legal moves to a single list.
Instead, consider keeping the current path as a local variable. For each legal move from the given square, you make a separate recursive call to find more moves. Each of those will add a legal move to the end of the local path.
As you return from each call, compare the best found path with the one you've been saving (the best so far); keep the better of those two, and go to the next legal move. When you've considered each of the four directions, then return the best-known path to the calling instance.
There are plenty of examples of backtracking on line and on this site; search out some that you find understandable. You might look under coin combination recursion (finding a set of coins that add to a certain amount) -- they're not the same algorithm, but the backtracking ideas above are shown more clearly.
Does that get you moving toward a solution?

Related

I am trying to change how many times a sequence changes direction

I am trying to create a program that adds to a specific variable when the list of integers change from positive to negative to neutral etc, but I keep getting the output of 8 instead of 7 but cannot find the bug. Please see my code below
def changing_direction(elements: list[int]) -> int:
#to check whether true or false with ones and zeros
x = 0
#to store last number to check with new number
a = 0
#to subtract last number from new number
b = 0
d = 0
e = 0
for i in elements:
b = a - i
if b > 0:
x = 1
elif b < 0:
x = 0
elif b != 0:
a = i
e = x
break
if x != e:
d += 1
a = i
e = x
return d
print("Example:")
print(changing_direction([6, 6, 6, 4, 1, 2, 5, 9, 7, 8, 5, 9, 4, 2, 6]))
You would need to keep track of the last direction - you can easily ignore all directions if they do not differ - no need to keep x.
You also need to keep track of the amount of changes.
This could be done like this:
def changing_direction(elems: list[int]) -> int:
# can not compare with 0 or 1 elem => 0 changes
if len(elems) < 2:
return 0
changes = 0
# helper, returns UP or DOWN or umplicit None based on b-a
def ud(a,b):
if b-a > 0: return "UP"
if b-a < 0: return "DOWN"
# current start direction
direction = None
for idx, e in enumerate(elems):
try:
# save old direction, initially None
ld = direction
# get new direction, maybe None
direction = ud(e,elems[idx+1])
print(direction) # --> comment for less output
# if both are not None and different we have a change in direction
# if either is None: same values this and last time round
#
if direction is not None and ld is not None and ld != direction:
changes += 1
if direction is None and ld is not None:
direction = ld # restore last direction for next time round
except IndexError:
pass # end of list reached
return changes
print("Example:")
print(changing_direction([6, 6, 6, 4, 1, 2, 5, 9, 7, 8, 5, 9, 4, 2, 6]))
to get:
Example:
None
None
DOWN
DOWN
UP
UP
UP
DOWN
UP
DOWN
UP
DOWN
DOWN
UP
7
Using proper variable names hopefully makes is easy to understand. If you store the "UP","DOWN" in a list you can also use that to count your changes by simply iterating over it.
For a more compact way to count this, look into list comprehensions.
You could simplify your function significantly by using zip() and list comprehension, which makes it return the correct value where the first change is not counted.
def changing_direction(elements: list[int]) -> int:
diffs = (i-j for i,j in zip(elements[:-1], elements[1:]) if i-j!=0)
return sum(i*j < 0 for i,j in zip(diffs[:-1], diffs[1:]))
zip(somelist[:-1], somelist[1:]) basically returns tuples containing consecutive elements as a generator.
We remove all the 0 elements because they cause problems in the following logic.
The main logic here is that 2 consecutive differences when multiplied together will be negative if the direction changes
sum here works to count things because True counts as a 1, and False counts as a 0
finally, you could have this as a 1 line lambda if you knew that lists did not have 2 consecutive items as the same value.
changing_direction = lambda seq: sum((i-j)*(j-k)<0 for i,j,k in zip(seq, seq[1:], seq[2:]))
such that
changing_direction([6, 6, 6, 4, 1, 2, 5, 9, 7, 8, 5, 9, 4, 2, 6])
retults in 7

Calculate total number of batteries used

Assume you have a phone, and several spare batteries.
arr1 => denotes the minutes for which every battery can last.
arr2 => denotes the minutes that every battery takes to be fully charged.
Once the battery is used up, you can charge it immediately and switch to the next fully charged battery.
You must use the batteries in the order the array denotes.
Suppose you will use the phone for x minute, return the number of batteries you will use.
If it is not possible to use the phone, then return -1
Example:
arr1 = [12, 3, 5, 18]
arr2 = [8, 1, 4, 9]
x = 16
output: 3
My Code:
arr1 = [12, 3, 5, 18]
arr2 = [8, 1, 4, 9]
x = 46 # getting correct result when x=16 but not when x=46
def solution(arr1, arr2, x):
import heapq
ready,charge=list(range(len(arr1))),[]
heapq.heapify(ready)
cur=res=0
while cur<x:
while charge and charge[0][0]<=cur:
heapq.heappush(ready, heapq.heappop(charge)[1])
if not ready:
return -1
i=heapq.heappop(ready)
res += 1
cur+=arr1[i]
heapq.heappush(charge,(cur+arr2[i],i))
return res
solution(arr1, arr2, x)
The code is giving an output 7.
But, the correct output is 5.
Here's an alternate function which doesn't involve iterating to find the solution. It computes the number of batteries required by looking at the total runtimes of the array of batteries, dividing x by the total runtime of all the batteries and then looking up the index of run time which will cover the balance (x % total_runtime). I've given a few ways of doing that lookup, dependent on what libraries (if any) are available.
In terms of whether the call can be completed, it looks at whether there is sufficient charge time (in the run time for the other batteries) for each battery before it has to be used again. If not, and the battery has to be used more than once, the call cannot be completed.
def solution(arr1, arr2, x):
# how many batteries?
numb = len(arr1)
# make cumulative sum of battery runtimes
runtimes = [sum(arr1[:i+1]) for i in range(numb)]
total_runtime = runtimes[numb-1]
# figure out how many batteries we need
batts = x // total_runtime * numb
x = x % total_runtime
if x > 0:
batts += bisect.bisect_left(runtimes, x) + 1
# or
# batts += np.searchsorted(runtimes, x) + 1
# or
# batts += next(idx + 1 for idx, value in enumerate(runtimes) if value >= x)
# check if any battery we use has a charge time greater than the total runtime of the other batteries
excess_charge_times = [total_runtime - runtime - arr2[idx] for idx, runtime in enumerate(arr1)]
# if a battery has to be used more than once and doesn't have enough charge time, fail
if any(batts > idx + numb and excess_charge_times[idx] < 0 for idx in range(numb)):
return -1
return batts
It seems you can implement this by simply looping through the sum of arr1 values until you reach x, maintaining a list of ready times for each battery so you can check whether the battery is currently available to use:
def solution(arr1, arr2, x):
numb = len(arr1)
count = 0
now = 0
ready = [0, 0, 0, 0]
while now < x:
batt = count % numb
if ready[batt] > now:
return -1
now += arr1[batt]
ready[batt] = now + arr2[batt]
count += 1
return count
solution([12, 3, 5, 18], [8, 1, 4, 9], 16)
# 3
solution([12, 3, 5, 18], [8, 1, 4, 9], 46)
# 5
solution([12, 3, 2], [6, 1, 1], 20)
# -1

Data Assignment and Scope Problem in Python

I have run into a very frustrating problem in Python. I am trying to make a sorting algorithm that works like so:
I read one variable from a list
I check how many variables have a lower value than that variable and assign that to a place variable
The current variable is put in its place
continue to the next variable and repeat
The problem is that the dummy variable automatically resets to zero after being incremented. I can't do step by step program running, so figuring out what's wrong is very difficult.
I have very little experience with python, so any help is greatly appreciated.
# sorting algorithm
theList = [9, 7, 8, 5, 6, 3, 4, 1, 2, -1]
def order(n):
a = 0
m = n
print(n)
print(m)
while a < len(n):
b = 0
c = 0 #this is where the problem is
while b < len(n):
if n[b] < n[a]:
c += 1 # after this if statement isn't true, c reverts to zero
print(c)
b += 1
#I think I should be able to put m[c] = n[a], but this doesn't work
a += 1
print(n)
print(m)
order(theList)
m = n
Does not create a new list. It just maps the name m to the same list that n points to. So when you do m[c] = n[a] the original list also changes - hence the unexpected behaviour.
Try:
def order(n):
a = 0
m = n[:] # create copy
print(n)
print(m)
while a < len(n):
b = 0
c = 0
while b < len(n):
if n[b] < n[a]:
c += 1
print(c)
b += 1
m[c] = n[a] # assign in output
a += 1
print(n)
print(m)
This does create a copy & seems to solve the issue:
...
[9, 7, 8, 5, 6, 3, 4, 1, 2, -1]
[-1, 1, 2, 3, 4, 5, 6, 7, 8, 9]
PS: sorting should not require a copy of the list to be made. There are better algorithms that can modify the list in-place.

Python list how to get item from last to first again?

I need to iterate sequentially until I find or maximize.
For example:
ds = [1,2,3,4,5,6,7,8,9]
tmp = 3 # start (variable)
max = 5 # maximize (variable)
target = 8
so output: [4,5,6,7,8]
Sorry, my english is not good.
As a very simple approach you could index over the concatenation with the same list.
However, from memory point of view certainly not the best solution.
# ds = [1,2,3,4,5,6,7,8,9]
start = 4
length = 7
res = (ds + ds)[start:start+length]
# [5, 6, 7, 8, 9, 1, 2]
There is a built-in way to do this.
new_data = i[starting_index : ending_index]
You can leave a number blank if you want to get the rest of the list. Like:
>>>i = [0,8,9,4]
>>>i[1:]
[8,9,4]
see this solution i used a for loop to reach your target
ds = [1,2,3,4,5,6,7,8,9]
tmp = 3 # start (variable)
max = 5 # maximize (variable)
target=8
i=0 # itiration to loop on the list
for x in ds:
if ds[i]<tmp: #till now we didnt reach start point
i=i+1
else:
print(ds[i])
i=i+1
if i == target: #since the target has been reached
break
Try:
>>> ds
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> (ds * ((5+6)//len(ds) + 1))[5:5+6]
[6, 7, 8, 9, 1, 2]
Now - 5 is your starting position 5+6 is your end position. You want to iterate over whole data set, as many times to contain end position, so:
((5+6)//len(ds) + 1)
In case if your starting position would be in second, or later repetition (so in your case if it would > 8. You can move it back by subtracting:
(startPosition//len(ds)) * len(ds)
from both start position, and end position.

python function not returning a list [duplicate]

This question already has answers here:
Why does my recursive function return None?
(4 answers)
Closed 7 months ago.
I can't figure out why my implementation of Reverse Cuthill–McKee algorithm is returning
at the wrong place.
Basically, it produces the correct result, but when I run the code, it returns a different value.
Here is a full code with a graph, could some one please explain this behavior?
import numpy as np
A=np.diag(np.ones(8))
nzc=[[4],[2,5,7],[1,4],[6],[0,2],[1,7],[3],[1,5]]
for i in range(len(nzc)):
for j in nzc[i]:
A[i,j]=1
# define the Result queue
R = ["C"]*A.shape[0]
def getDegree(Graph):
"""
find the degree of each node. That is the number
of neighbours or connections.
(number of non-zero elements) in each row minus 1.
Graph is a Cubic Matrix.
"""
degree = [0]*Graph.shape[0]
for row in range(Graph.shape[0]):
degree[row] = len(np.flatnonzero(Graph[row]))-1
return degree
# find the starting point, the parent
degree = getDegree(A)
adj = getAdjcncy(A)
degree2=degree[::]
adj2 = adj[::]
digar=np.array(degree2)
pivots = list(np.where(digar == digar.min())[0])
def main_loop2(deg,start, adj,R):
degcp=deg[:]
digar=np.array(deg)
# use np.where here to get indecies of minimums
if start not in R:
R.append(start)
degcp.pop(start)
if start in pivots:
pivots.pop(start)
print "p: ", pivots
Q=adj[start]
for idx, item in enumerate(Q):
if item not in R:
R.append(item)
print "R", R
print "starts", adj[R[-1]]
Q=adj[R[-1]]
if set(Q).issubset(set(R)) and len(R) < len(degcp) :
print "will now look for new pivot"
p = pivots[0]
pivots.pop(0)
print "pivots", pivots
print "starting with" , p
main_loop2(deg,p,adj,R)
return 'this is never reached'
elif len(R) < len(degcp):
main_loop2(deg,R[-1],adj,R)
return 'wrong'
else:
print "will return R"
print type(R)
return R
inl=[]
Res = main_loop2(degree2,0, adj,inl)
print(Res)
output:
Degrees: [1, 3, 2, 1, 2, 2, 1, 2]
0
p: [3, 6]
R [0, 4]
starts [0, 2]
R 2 degcp 7
R [0, 4, 2]
starts [1, 4]
R 3 degcp 8
R [0, 4, 2, 1]
starts [2, 5, 7]
R 4 degcp 8
R [0, 4, 2, 1, 5]
R [0, 4, 2, 1, 5, 7]
starts [1, 5]
will now look for new pivot
pivots [6]
starting with 3
R [0, 4, 2, 1, 5, 7, 3, 6]
starts [3]
will return R
<type 'list'>
wrong
So the question is:
Why is the function working OK at all cycles of the recursion, but in the last one,
it returns the value wrong?
The output shows that the last else is only reached once, yet, a list is not returned, and I am quite helpless here.
I would appreciate if someone shed some light on this.
The top level of your recursive call goes into here:
elif len(R) < len(degcp):
main_loop2(deg,R[-1],adj,R)
return 'wrong'
Whatever happens further inside the recursive call to main_loop2 doesn't matter; when it comes back out, it will execute the return 'wrong' statement. You need to actually return the value the innermost call produces, all the way back out to the top level - it's not going to magically happen for you.
Did you mean to return main_loop2(...)?

Categories

Resources