Order of operations with recursion confusing - python

In this piece of code
def maxVal(toConsider, avail):
if toConsider == [] or avail == 0:
result = (0, ())
elif toConsider[0].getCost() > avail:
result = maxVal(toConsider[1:], avail)
else:
nextItem = toConsider[0]
withVal, withToTake = maxVal(toConsider[1:],
avail - nextItem.getCost())
withVal += nextItem.getValue()
withoutVal, withoutToTake = maxVal(toConsider[1:], avail)
if withVal > withoutVal:
result = (withVal, withToTake + (nextItem,))
else:
result = (withoutVal, withoutToTake)
return result
How is withVal not some sort of infinite loop because it gets it's assigned variables from the function, which means it repeats again without the first variable so shouldn't it just keep going and going? Until avail reaches 0 but then it also is added to 'nextItem' which drives my head in.
So I know that
withVal, withToTake = maxVal(toConsider[1:],
avail - nextItem.getCost())
comes before
withVal += nextItem.getValue()
And doesn't for an infinite loop, so my question is how is withVal able to stay in order with 2 assignments considering the first example demonstrates that it recalls the function again?

This function is guaranteed to terminate since avail == 0 isn't the only stopping condition. It also suffices to have toConsider == []. When you call the code
withVal, withToTake = maxVal(toConsider[1:],
avail - nextItem.getCost())
Notice in particular the snippet [1:]. This says that you're only passing in most of the toConsider list. It's getting smaller each time by 1, and eventually its length will hit 0. Since the recursion eventually ends, you're able to finally get to the next assignment withVal += nextItem.getValue().
Sometimes getting to the bottom of questions like this can be made easier in the beginning with pythontutor.com, which allows you to step through and visualize code one piece at a time and see which values change in which order. I'm not affiliated with them, but they have an okay product for small problems like this.

Related

Python how do i make list appends / extends quicker?

Heyo all.
Trying to get better at python and started doing leetcode problems.
Im currently doing one, were the goal is to capture water.
Link => https://leetcode.com/problems/trapping-rain-water/
problem is; it times me out for taking too long. My code is certainly inefficient. Afer googling around i found that .append is supposedly very slow / inefficient. So is .extend.
Cant find any obvious ways of making my code faster; hence my arrival here.
any response is much appreciated
class Solution:
def trap(self, height: List[int]) -> int:
max_height = max(height)
water_blocks = 0
for element in range(max_height):
local_map = []
element = element + 1
for block in height:
if block >= element:
local_map.extend([1])
else:
local_map.extend([0])
if local_map.count(1) > 1:
first_index = local_map.index(1)
reversed_list = local_map[::-1]
last_index = len(local_map) - 1 - reversed_list.index(1)
water_count = last_index - first_index - 1 - (local_map.count(1) - 2)
water_blocks += water_count
else:
continue
return water_blocks
Although many of your count and index calls can be avoided, the two big nested loops might still be a problem. For the outer loop, max_height can be large number and the inner loop iterates over the full list. You might need to come up with a different algorithm.
I don't have a leetcode account, so I can't really test my code, but this would be my suggestion: It iterates over the height-list only once, with a small inner loop to find the next matching wall.
class Solution:
def trap(self, h):
water = 0
current_height = 0
for i, n in enumerate(h):
# found a "bucket", add water
if n < current_height:
water += current_height - n
else: # found a wall. calculate usable height
current_height = self.n_or_max(h[i+1:], n)
return water
def n_or_max(self, h, n):
local_max = 0
for i in h:
if i > local_max:
local_max = i
# that's high enough, return n
if i >= n:
return n
return local_max
Here are some pointers:
Do not use list.count() or list.index() (that is, try to remove local_map.count(1), local_map.index(1) and reversed_list.index(1)). The first will loop (internally) over the whole list, which is obviously expensive if the list is large. The second will loop over the list until a 1 is found. Currently you even have two calls to local_map.count(1) which will always return the same answer, so at least just store the result in a variable. In your loop over blocks, you construct local_map yourself, so you do in fact know exactly what it contains, you should not have to search through it afterwards. Just put a few ifs into the first loop over blocks.
The operation local_map[::-1] not only runs over the whole list, but additionally copies the whole thing into a new list (backwards, but that's not really contributing to the issue). Again, this new list does not contain new information, so you can figure out the value of water_count without doing this.
The above is really the major issues. A slight further optimization can be obtained by eliminating element = element + 1. Just shift the range, as in range(1, max_height + 1).
Also, as written in the comments, prefer list.append(x) to list.extend([x]). It's not huge, but the latter has to create an additional list (of length 1), put x into it, loop over the list and append its elements (just x) to the large list. Finally, the length-1 list is thrown away. On the contrary, list.append(x) just appends x to the list, no temporary length-1 list needed.
Note that list.append() is not slow. It's a function call, which is always somewhat slow, but the actual data operation is fast: constant time and even cleverly amortized, as juanpa.arrivillaga writes.
Here's another way of looking at the problem. This scans left to right over the bins, and at each point, I track how many units of water are dammed up at each level. When there's a tall wall, we tally up whatever units it was damming, and clear them. However, this still gets an "overtime" flag on the next to the last test, which has about 10,000 entries. It takes 20 seconds on my relatively old box.
class Solution():
def trap(self, height):
trapped = 0
accum = [0]*max(height)
lastwall = -1
for h in height:
# Take credit for everything up to our height.
trapped += sum(accum[0:h])
accum[0:h] = [0]*h
for v in range(h,lastwall):
accum[v] += 1
lastwall = max(lastwall,h)
return trapped
print(Solution().trap([0,1,0,2,1,0,1,3,2,1,2,1])) # 6
print(Solution().trap([4,2,0,3,2,5])) # 9

Recursion with for loops does not return anything

I have this recursion function I wrote (Simplified, it is much longer but the idea stays the same)
def rec(point,length):
if 1<length<=8:
str_adj = '23456'
for adj in str_adj:
return rec(adj, length-1)
elif length == 1:
return 1
else:
return 0
rec('1',2)
[Disclaimer: I know the code looks weird and some parts are not needed because I simplified the function. ]
Now, when I run this code I get: 1, however the output needs to be 5 as I iterate over each letter of the string '23456' (which has 5 letters) and for each letter I call it with length of 1 (as I decrease it 2-1=1) and each time we get length=1 we return 1, so it should return 1 five times..
I've sat down to debug and break down the code into multiple parts and tried many many different version of it, such as removing 'return' in the loop, but when doing it, the output is None...
Can someone please help me to spot the mistake? thanks!
In python code, I am trying to do the following:
rec('2',1)+rec('3',1)+rec('4',1)+rec('5',1)+rec('6',1) = 1 + 1 + 1 + 1 +1 = 5
as each time we call rec(?,1) = 1
Let's say the str_adj = '123456789' and length = 2
then I want to output to be:
rec('1',1)+rec('2',1)+ etc.. until + rec('9',1)
each time we call rec(?,1) = 1 so it should return 9
I'm not sure what the end goal is, but your logic is breaking the code. When you call rec the first time, it enters the first "if" statement, and from there calls
rec('2', 1)
This call of the function enters the "elif" statement, returns 1, and then ends.
If you could clarify what you are trying to accomplish I can try to help fix this logic.
Your expected behavior does not match what is happening. You're never modifying str_adj and setting str_adj each time to equal 23456. In your for loop, adj will always be 2 unless you change it. In order to do so, it will also have to not be a variable local to the function.
You will also need something to keep track of your iterations.
Doing your example like this, will give you the result you want. You could make it much better, but I did my best to change it and match your format.
def rec(point,length, str_adj, iterations):
print("rec(", point, ", ", length, ", ", str_adj, ", ", iterations, ")")
if 1<length<=8:
for adj in str_adj:
str_adj = str_adj[1:]
iterations = iterations + 1
return rec(adj, length-1, str_adj, iterations)
elif length == 1:
return iterations
else:
return 0
iterations = 1;
str_adj = '23456'
print(rec('1',4, str_adj, iterations))

optimizing code running time [diffrence between the codes below]

these are two codes, can anyone tell me why the second one takes more time to run.
#1
ar = [9547948, 8558390, 9999933, 5148263, 5764559, 906438, 9296778, 1156268]
count=0
big = max(ar)
for i in range(len(ar)):
if(ar[i]==big):
count+=1
print(count)
#2
ar = [9547948, 8558390, 9999933, 5148263, 5764559, 906438, 9296778, 1156268]
list = [i for i in ar if i == max(ar)]
return len(list)
In the list comprehension (the second one), the if clause is evaluated for each candidate item, so max() is evaluated each time.
In the first one, the maximum is evaluated once, before the loop starts. You could probably get a similar performance from the list comprehension by pre-evaluating the maximum in the same way:
maxiest = max(ar)
list = [i for i in ar if i == maxiest]
Additionally, you're not creating a new list in the first one, rather you're just counting the items that match the biggest one. That may also have an impact but you'd have to do some measurements to be certain.
Of course, if you just want to know what the difference between those two algorithms are, that hopefully answers your question. However, you should be aware that max itself will generally pass over the data, then your search will do that again. There is a way to do it with only one pass, something like:
def countLargest(collection):
# Just return zero for empty list.
if len(collection) == 0: return 0
# Setup so first item is largest with count one.
count = 0
largest = collection[0]
# Process every item.
for item in collection:
if item > largest:
# Larger: Replace with count one.
largest = item
count = 1
elif item == largest:
# Equal: increase count.
count += 1
return count
Just keep in mind you should check if that's faster, based on likely data sets (the optimisation mantra is "measure, don't guess").
And, to be honest, it won't make much difference until either your data sets get very large or you need to do it many, many times per second. It certainly won't make any real difference for your eight-element collection. Sometimes, it's better to optimise for readability rather than performance.

List arithmetic operation in python

one problem statement on python list arithmetic that is I have one list say,
my_set = [24565,24621,32,598,899]
I want to take difference between first two elements and if difference is in range of -127 to 127 then add +128 to next index position and so on. First element of the list stays as-is
Like this the output
[24565, 56, +128, 24589, +128, −566, +128, -301].
this is what I tried to do
def inRange(val):
return val in range(-127,127)
my_set = [24565,24621,32,598,899]
for i in range(len(my_set)-1):
res = my_set[i] - my_set[i+1]
if inRange(res) == True:
my_set.insert(i+1,res)
my_set.insert(i+2,128)
print(my_set)
Please tell me how to do that.??
THankyou!
But in the desired output that you have written, you are adding 128 in spite of what the difference is. And you are also adding the difference value to the list. I got confused there. Anyways, this does adding the difference and 128 to the list. Also, can you make use of a new list to update the output or should you update the same list? The first case, its easy; the second case, you can try the below code
def inRange(val): # a simple if is enough and faster range
if val < 127 and val >= -127:
return True
my_set = [24565,24621,32,598,899]
#as you wanted the first element as is, moved this piece of code out of the loop
my_set.insert(1, my_set[0]-my_set[1])
my_set.insert(2,128)
i = 3
while i < len(my_set) - 1:
res = my_set[i] - my_set[i+1]
#if inRange(res) == True: # you can add this loop if required and do the inserts accordingly
my_set[i] = res
my_set.insert(i+1, 128)
i = i + 2 # if you are gonna add the if check and insert only then, you might want to change the i increment as per that
print(my_set)
Hope this helps. And expecting some one else to give a better answer :)

why my code stop in secound while loop?

#hello , i wounder why my code keep stoping at the secound while loop and doesn't do anything
print"*******************************"
a = 0
deg_list =[]
deg_list_a=[]
deg_list_b=[]
deg_list_c=[]
degree=input("Enter the students Degree:")
while a<=degree:
deg_list.append(degree);
degree=input("Enter the students Degree:")
print "Degree List :",deg_list
print len(deg_list)
while len(deg_list)>=0:
if deg_list[a]>=16:
deg_list_a.append(deg_list[a])
x=+1
elif 15>deg_list[a]>=10:
deg_list_b.append(deg_list[a])
x=+1
else :
deg_list_b.append(deg_list[a])
x=+1
print deg_list_a
print deg_list_b
print deg_list_c
Your code enters an endless loop.
Both of your while loops have problems with the condition which allows them to terminate. Since your code never changes the value of a, the first loop becomes while 0<=degree, and so the first loop terminates when the user inputs a negative value. But the variable a can be removed from your program.
The while loop continues as long as len(deg_list) >= 0. However, no code within the loop decreases the length of deg_list, so the while loop continues forever.
The code below could help you get this working:
deg_list =[]
deg_list_a=[]
deg_list_b=[]
deg_list_c=[]
degree=input("Enter the students Degree:")
while degree > 0:
deg_list.append(degree);
degree=input("Enter the students Degree:")
print len(deg_list)
while len(deg_list) > 0: # Strictly greater than 0, not equal to 0.
if deg_list[0] >= 16:
# Use pop to access first element
deg_list_a.append(deg_list.pop(0))
elif deg_list[0] >= 10: # One comparison per statement, only.
deg_list_b.append(deg_list.pop(0))
else:
deg_list_c.append(deg_list.pop(0)) # c, not b.
print deg_list_a
print deg_list_b
print deg_list_c
You're never modifying deg_list, so your loop becomes infinite. Even removing all elements wouldn't help since you're comparing against 0 -- the loop condition will never be false.
Well.
It looks to me that a is set to 0 in the beginning and then never changed, so doing something with deg_list[a], that is the first element in the list, isn't going to do very much. In addition, your looping condition is len(deg_list) >= 0, and len(deg_list) will never change.
But there are more fundamental issues with your code. Imagine you were changing the length of deg_list: in this case you would be changing the very list you're looping over, which is usually (if you are not very very certain what you're doing) a recipe for disaster. What I think you should envisage doing is a loop along the lines of:
for degree in deg_list:
if [degree fulfils some condition]:
[do something with degree]
elif [degree fulfils some other condition]:
[do something else]
...
else:
[whatever]
Last, from your comparison it seems that the "degrees" are all small integers. You may want to test for that -- it's user input and you have to expect anything being thrown at your input -- before doing things like if degree >= 16.
It looks like you are trying to loop through all the members of deg_list, but you are waiting for deg_list to be empty, and each time through the loop you are incrementing "x," which is never even read.
If you really are trying to traverse through deg_list, try this for your second loop:
for degree in deg_list:
if degree >= 16:
deg_list_a.append(degree)
elif degree >= 10:
deg_list_b.append(degree)
else :
deg_list_c.append(degree)

Categories