Cannot understand recursive function Python - python

I have been looking at the solution for this problem (below) for hours and cannot figure out how the recursion works. Can someone please explain in basic terms how it works.
Since group is appended and then popped, isn't the popped list going to just always be equal to nothing []?
# Given a list of ints, is it possible to choose a group of some of the
# ints, such that the group sums to the given target?
# 0, 2, 4, 8, 10 -> True
# 0, 2, 4, 8, 14 -> True
# 0, 2, 4, 8, 9 -> False
def sum_checker(ints, total, group, index):
if sum(group) > total: # backtracking
return False
if index == len(ints): # BASE CASE
return sum(group) == total
group.append(ints[index])
if sum_checker(ints, total, group, index + 1):
return True
group.pop()
return sum_checker(ints, total, group, index + 1)
ints = [int(input()) for i in range(int(input()))]
total = int(input())
group = []
print(sum_checker(ints, total, group, 0))

isn't the popped list going to just always be equal to nothing []?
Not always. At the third if statement, it recursively adds to the group list, and only when that call stack returns (when the sum of the group exceeds the total, or the index is outside the input range), does the most recent pushed value get popped.
In execution order of the first input , here's the group values
0
0, 2
0, 2, 4
0, 2, 4, 8... Sum is greater than 10, return False
8 is popped, and the previous index increases
0, 2, 4.... The index is out of range, and the group sum is not the total
4 is popped and the previous index increases
0, 2, 8... This is equal to 10, so we return True back up the call stack
At the end of unwinding the recursion, yes the group list will be empty, but the output only requires a boolean, not tracking the group that matches the criteria

Ok. So let's think this question without real code at first. Just think this strategy:
We test each element of ints one by one, checking by picking current value plus previous selection can we get the target sum.
For example, ints= [1, 2, 4, 8], target = 11.
We try to select 1, we don't know if we can get the target before examine other elements, so we continue to check, holding that 1 in your hands.
Now we meet 2, still no idea, just pick and continue.
Same for 4.
Oops, we have sum=1+2+4+8 = 13 > 11. [1,2,4,8] is a bad selection plan. Let's drop from the end and chek if we have other choices. Drop 8.
Now we have sum=1+2+4=7, but we cannot add more elements since we reach the end of ints. Drop 4 to see if more choices are possible.
We now hold 1+2 and should check from 8, That's 11! We made it!
So try this process yourself using pencil and paper. The core idea here is to maintain a what-we-have-selected array, which is group in your code, and then to check if we can reach target based on this selection + new elements. If not, modify the what-we-have-selected array and continue.

Regarding your concern about group being appended and popped. Remember that the statement
if sum_checker(ints, total, group, index + 1):
Starts the search again (just this time with the next index). Say with your example.
sum_checker(
ints=[0, 2, 4, 8],
total=10,
group=[],
index=0
)
The first time we hit group, we append 0 to group, so we're then calling sum_checker with the arguments.
sum_checker(
ints=[0, 2, 4, 8],
total=10,
group=[0],
index=1
)
Then, since group already has one element, we append 2 to group. So we then have:
sum_checker(
ints=[0, 2, 4, 8],
total=10,
group=[0, 2],
index=2
)
Which means, we start filling up group before we get to .pop(). If the sum() of group becomes too big, THEN we do the first .pop(), and start checking again without the last element that was added.

Related

Python find index of last element smaller than number

I have a list of numbers from 0 to 3 and I want to remove every number that is smaller than 2 xor is not connected to the last 3 in the list. It is also going to be done about 200 Million times so it should preferably perform well. For example, I could have a list like that:
listIwantToCheck = [3, 0, 1, 2, 0, 2, 3, 2, 2, 3, 2, 0, 2, 1]
listIWantToGet = [2, 3, 2, 2, 3]
I already have the index of the last 3 so what I would do is:
listIWantToGet = listIWantToCheck[??? : indexOfLastThree + 1]
??? being 4 in this instance. It is the index with the mentioned conditions.
So How do I get the index of the last number smaller than 2?
Nailed it, the index i want is
index = ([0]+[i for i, e in enumerate(listIWantToCheck[:indexOfLastThree]) if e < 2])[-1] + 1
List comprehension is truly beautiful.
I enumerated through the slice and created a list of all indices, which point to a number smaller than 2 and took the last. The 0 in front is added to circumvent an index error, which would occur if there are no elements smaller than 2.
So if i get this right you want to get the index of the last number smaller than 2 that comes before the last 3.
My approach would be to take the part of the list from index 0 to the index of the last 3 and then reverse the list and check if the number is smaller than 2.
If you however want to get the last 2 of the entire list just reverse it and loop through it the same way
for i in listIWanttoCheck[0:indexOfLastThree].reverse():
if i <2:
return listIWanttoCheck.index(i)
Correct me if I missunderstood your problem

Non-tail recursion within a for loop

Given an array of numbers, find the length of the longest increasing subsequence in the array. The subsequence does not necessarily have to be contiguous.
For example, given the array [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15], the longest increasing subsequence has length 6: it is 0, 2, 6, 9, 11, 15.
One of the solutions to the above problem uses non-tail recursion within a for loop, and I am having trouble making sense of it. I don't understand when the code after the recursive call in the for loop is executed, and I can't visualize the entire execution process of the whole solution.
def longest_increasing_subsequence(arr):
if not arr:
return 0
if len(arr) == 1:
return 1
max_ending_here = 0
for i in range(len(arr)):
ending_at_i = longest_increasing_subsequence(arr[:i])
if arr[-1] > arr[i - 1] and ending_at_i + 1 > max_ending_here:
max_ending_here = ending_at_i + 1
return max_ending_here
The description of the solution is as follows:
Assume that we already have a function that gives us the length of the longest increasing subsequence. Then we’ll try to feed some part of our input array back to it and try to extend the result. Our base cases are: the empty list, returning 0, and an array with one element, returning 1.
Then,
For every index i up until the second to last element, calculate longest_increasing_subsequence up to there.
We can only extend the result with the last element if our last element is greater than arr[i] (since otherwise, it’s not increasing).
Keep track of the largest result.
Source: https://www.dailycodingproblem.com/blog/longest-increasing-subsequence/
**EDITS**:
What I mean by I don't understand when the code after the recursive call in the for loop is executed. Here is my understanding:
Some code calls lis([0, 8, 4, 12, 2]).
arr = [0, 8, 4, 12, 2] doesn't meet either of the two base cases.
The for loop makes the first call when i = 0 in the line ending_at_i = lis([]). This is the first base case, so it returns 0. I can't understand why control doesn't return to the for loop so that ending_at_i is set to 0, and the if condition is executed (because it surely isn't checked else [][-1] would throw an error), after which we can move on to the for loop making the second call when i = 1, third call when i = 2 which would branch into two calls, and so on.
Here's how this function works. Fist, it handles the degenerate cases where the list length is 0 or 1.
It then looks for the solution when the list length is >= 2. There are two possibilities for the longest sequence: (1) It may contain the last number in the list, or (2) It may not contain the last number in the list.
For case (1), if the last number in the list is in the longest sequence, then the number before it in the longest sequence must be one of the earlier numbers. Suppose the number before it in the sequence is at position x. Then the longest sequence is the longest sequence taken from the numbers in the list up to and including x, plus the last number in the list. So it recurses on all of the possible positions of x, which are 0 through the list length minus 2. It iterates i over range(len(arr)), which is 0 through len(arr)-1). But it then uses i as the upper bound in the slice, so the last element in the slice corresponds to indices -1 through len(arr)-2. In the case of -1, this is an empty slice, which handles the case where all values in the list before the last are >= the last element.
This handles case (1). For case (2), we just need to find the largest sequence from the sublist that excludes the last element. However, this check is missing from the posted code, which is why the wrong answer is given for a list like [1, 2, 3, 0]:
>>> longest_increasing_subsequence([1, 2, 3, 0])
0
>>>
Obviously the correct answer in this case is 3, not 0. This is fairly easy to fix, but somehow was left out of the posted version.
Also, as others have pointed out, creating a new slice each time it recurses is unnecessary and inefficient. All that's needed is to pass the length of the sublist to achieve the same result.
Here is a (hopefully good enough) explanation:
ending_at_i = the length of the LIS when you clip arr at the i-th index (that is, considering elements arr[0], arr[1], ..., arr[i-1].
if arr[-1] > arr[i - 1] and ending_at_i + 1 > max_ending_here
if arr[-1] > arr[i - 1] = if the last element of arr is greater than the last element of the part of arr correponding to ending_at_i
if ending_at_i + 1 > max_ending_here = if appending the last element of arr to the LIS found during computing ending_at_i is larger than the current best LIS
The recursive step is then:
Let an oracle tell you the length of the LIS in arr[:i] (= arr[0], arr[1], ..., arr[i-1])
realize that, if the last element of arr, that is, arr[-1], is larger than the last element of arr[:i], then whatever the LIS inside arr[:i] was, if you take it and append arr[-1], it will still be an LIS, except that it will be one element larger
Check whether arr[-1] is actually larger than arr[i-1], (= arr[:i][-1])
Check whether appending arr[-1] to the LIS of arr[:i] creates the new optimal solution
Repeat 1., 2., 3. for i in range(len(arr)).
The result will be the knowledge of the length of the LIS inside arr.
All that being said, since the recursive substep of this algorithm runs in O(n), there are very few worse feasible solutions to the problem.
You tagged dynamic programming, however, this is precisely the anti-example of such. Dynamic programming lets you reuse the solutions to subproblems, which is precisely what this algorithm doesn't do, hence wasting time. Check out a DP solution instead.

Index out of range confusion

So i am new to programming, and i am having trouble with index out of range errors. Quick example:
I have a list, lst = (5,7,8,9,10).
I want to remove every even number, and every number to the right of an even number.
I would approach this problem by getting the index of every even number, 'i' , and removing lst[i] and lst [i+1]. That will not work when the last number is even because there is no lst [i+1] after the last element in the list.
I have run into this issue on several basic problems i have been working on. My approach to solving this is probably wrong, so i would like to know:
How can i/Can i solve the problem this way, whether it is efficient or not?
What would be the most efficient way to solve this problem?
Welcome to the club! Programming is a lot of fun and something you can always improve upon with incremental progress. I'm going to try to be exhaustive with my answer.
With lists (also known as arrays) remember that a list and its indexes are zero-based. What this means is that an array's indexes start at the number 0 (not number 1 like you would do in normal counting).
arr = [5, 7, 8, 9, 10]
# If you want to access the first element of the array
# then you would use the 0 index. If you want the Second
# element you use index 1.
print(arr[0]) # prints 5 or the 1st element
print(arr[1]) # prints 7 or the 2nd element
I would not use your stand looping technique like for or while in this case because you are removing elements are you are going for the array. If you delete the item as you are looping you are changing the length of the array.
Instead, you could create a new array from looping and only adding or appending odd values to this new array.
arr = [5, 7, 8, 9, 10]
new_arr = []
for idx, val in enumerate(arr):
if idx % 2 == 1:
new_arr.append(val)
return new_arr # yields [7,9] or this process creates a new array of odd elements
In addition, remember when you are using [i+1] while you are indexing through loop in makes sense to stop the loop an element early to avoid an out of index range error.
Do this (no error)
for idx in range(len(arr)-1):
# pseudocode
print(arr[i] + arr[i+1])
instead of this (out of index error). The reason being is that on the last element if you try to add 1 to last index and then access a value that does not exist then an error will be returned:
for idx in range(len(arr)):
# pseudocode
print(arr[i] + arr[i+1])
arr = [5, 7, 8, 9, 10]
# if you try to access arr[5]
# you will get an error because the index
# and element do not exist
# the last element of arr is arr[4] or arr[-1]
arr[5] # yields an out of index error
There are many Pythonic (almost like a colloqial phrase specific to python) ways to accomplish your goal that are more efficient below.
You can use slicing, spacing and the del (delete statment) to remove even number elements
>>> arr = [5, 7, 8, 9, 10]
>>> del arr[::2] # delete even numbers # if you wanted to delete odd numbers del arr[1::2]
>>> arr
[7, 9]
Or a list comprehension to create a new list while looping through some conditional to filter the even numbers out:
new_arr = [elem for idx, elem in enumerate(arr) if idx % 2 == 0]
The % operator is used to see if there is a remainder from division. So if idx is 10. Then 10 % 2 == 0 is true because 2 is able to divide into 10 five times and the remainder is 0. Therefore, the element is even. If you were checking for odd the condition would be:
idx % 2 == 1
You can find further explanation of these Python methods from this great Stack Overflow post here
One issue you may run into is your list indexes shifting on you during removal. One way around this is to sort the indexes to be removed in descending order and remove them first.
Here is an example of how you could accomplish what you are looking for:
myList = [5, 7, 8, 9, 10]
# use list comprehension to get indexes of even numbers into a list.
# num % 2 uses the modulus operator to find numbers divisible by 2
# with a remainder of 0.
even_number_indexes = [idx for idx, num in enumerate(myList) if num % 2 == 0]
# even_number_indexes: [2, 4]
# sort our new list descending
even_number_indexes.reverse()
# even_number_indexes: [4, 2]
# iterate over even_number_indexes and delete index and index + 1
# from myList by specifying a range [index:index + 2]
for index in even_number_indexes:
del myList[index:index + 2]
print(myList)
output: [5, 7]
You can check if i+1 is greater than (Edit: or equal to) the length of the list, and if it is, not execute the code.
You can also handle this in a try/except block.
As to the efficiency of this method of solving, seems fine to me. One gotcha in this approach is that people try to iterate over the list while modifying it, which can lead to unknown errors. If you're using the remove() function, you probably want to do it with a copy of the list.

starting range() as len(<some_list>) VS starting range() as len(<some_list>)-1 [Inclusive/Exclusive end]

I can't seem to understand when does the end condition is included and when it's not depending of the start as a length of list or length of list deducted by one (last position) , without modifying the end with the step value
I wanted to pop elements from a list within a loop
(Note: I know that setting range(0,len(colors_list)) like this will do the trick too)
colors_list = ["green","blue","yellow","pink","violet","black"]
I tried this snippet of code, and the end was included :-
for color in range(len(colors_list),0,-1):
colors_list.pop()
print(colors_list)
Output : []
and I tried this too, but here the end was excluded :-
for color in range(len(colors_list)-1,0,-1):
colors_list.pop()
print(colors_list)
Output : ["green"]
I understand the second trial as the 0th index is not popped, but the first trial is what I dont understand, like isn't it supposed to be the same as the second ? considering that it stop befrore the 0th index ? but the first element is popped instead.
The end was excluded in both cases. You ran the loop one fewer times in the second case, because you added the -1 to the start condition. The end value is always exclusive, the start value is always inclusive.
Just listify and print the ranges and you'll see:
>>> colors_list = ["green","blue","yellow","pink","violet","black"]
>>> print(list(range(len(colors_list),0,-1)))
[6, 5, 4, 3, 2, 1]
>>> print(list(range(len(colors_list)-1,0,-1)))
[5, 4, 3, 2, 1]
No 0 on either, you just began the loop from 6 on one, and 5 on the other.

Traversing a Python list and making in-place changes

My task is to remove all instances of one particular element ('6' in this example) and move those to the end of the list. The requirement is to traverse a list making in-line changes (creating no supplemental lists).
Input example: [6,4,6,2,3,6,9,6,1,6,5]
Output example: [4,2,3,9,1,5,6,6,6,6,6]
So far, I have been able to do this only by making supplemental lists (breaking the task's requirements), so this working code is not allowed:
def shift_sixes(nums):
b = []
c = 0
d = []
for i in nums:
if i == 6:
b.insert(len(nums),i)
elif i != 6:
c = c +1
d.insert(c,i)
ans = d + b
return ans
I've also tried list.remove() and list.insert() but have gotten into trouble with the indexing (which moves when I insert() then move the element to the end): For example -
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in nums:
if i == 6:
nums.remove(i)
nums.insert(nums[len(nums)-1], 0)
elif i != 0:
i
shift_sixes(a)
Additionally, I have tried to use the enumerate() function as follows, but run into problems on the right hand side of the b[idx] assigment line:
for idx, b in enumerate(a):
a[idx] = ???
Have read other stackoverflow entries here, here and here, but they do not tackle the movment of the element to one end.
Would appreciate any help on this list traversal / inplace switching issue. Many thanks.
EDIT
#eph - thank you. this is indeed an elegant response. I am sure it will pass my 'no new list' requirement? I surely intend to learn more about lambda and its uses
#falsetru - thank you for the reminder of the append/pop combination (which I tried to do in my original query via list.remove() and list.insert()
#tdelaney - thank you as well. somehow your response is closest to what I was attempting, but it seems not to pass the test for [0, 0, 5].
It is a bad idea to modify list while traverse. You can either make a copy to traverse, or generate a new list during traverse.
In fact, the question can be done in many ways, such as:
>>> a.sort(key = lambda i: i == 6)
>>> a
[4, 2, 3, 9, 1, 5, 6, 6, 6, 6, 6]
Iterating the list reverse way, pop the element if it's 6, then append it.
xs = [6,4,6,2,3,6,9,6,1,6,5]
for i in range(len(xs)-1, -1, -1): # 10 to 0
if xs[i] == 6:
xs.append(xs.pop(i))
Why not try something like this?
Basically, the approach is to first count the number of values.
If 0, then returns (since Python produces a ValueError if the list.index method is called for an element not in the list).
We can then set the first acceptable index for the value to be the length of the list minus the number of occurrences it exists in the list.
We can then combine list.pop/list.append to then traverse the list until all the values desired occur at the end of the list.
def shift_value(lst, value):
counts = lst.count(value) # 5
if not counts:
return lst
value_index = len(lst) - counts
index = lst.index(value)
while index != value_index:
lst.append(lst.pop(index))
index = lst.index(value)
return lst
lst = [6,4,6,2,3,6,9,6,1,6,5]
print(shift_value(lst, 6))
EDIT: This is horribly inefficient, better answer suggested above.
This requires O(n^2) time, rather than O(n) time.
The key term here is "In Line". The way you do that is move num[i] = num[i+1] for each i to the end of the list.
def shift_sixes(num):
for i, val in enumerate(num):
if val == 6:
# shift remaining items down
for j in range(i,len(num)-1):
num[j] = num[j+1]
# add 6 at the end
num[-1] = 6
return num
print(shift_sixes([1,9,4,6,2,7,8,6,2,2,6]))
print(shift_sixes([1,2,3]))
print(shift_sixes([6]))
print(shift_sixes([3]))
Use two runners. First from front to end checking for 6s, second from end to front pointing to last item that's not a 6. Keep swapping (a[i+1], a[i] = a[i], a[i+1]) until they meet.
Catch: this is not stable like in a stable sort. But I don't see that as a requirement.
Will try to write working code when in front of a python interpreter with a keyboard.
In case you need a stable sort (i.e. order of elements that are not 6 should remain the same), then the solution is:
def move_to_end(data, value):
current = 0 # Instead of iterating with for, we iterate with index
processed = 0 # How many elements we already found and moved to end of list
length = len(data) # How many elements we must process
while current + processed < length: # While there's still data to process
if data[current] == value: # If current element matches condition
data.append(data.pop(current)) # We remove it from list and append to end
processed += 1 # Our index remains the same since list shifted, but we increase number of processed elements
else: # If current element is not a match
current += 1 # We increase our index and proceed to next element
if __name__ == '__main__':
print
print 'Some testing:'
print
for test_case in (
[1, 9, 4, 6, 2, 7, 8, 6, 2, 2, 6], # Generic case
[6, 6, 6, 6], # All items are 6
[1, 7], # No items are 6
[], # No items at all
):
print 'Raw:', test_case
move_to_end(test_case, 6)
print 'Becomes:', test_case
print
Note that this solution retains the order of not only non-matching elements, but of matching elements as well. So for example, if you change the check condition from "equal to 6" to "is an even number", all elements matching the condition will be moved to the end of list while retaining their order among themselves.
Why not keep it simple?
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in range(0,len(nums)):
if nums[i] == 6:
nums.append(nums.pop(i))
>>> shift_sixes(a)
>>> a
[3, 9, 1, 5, 2, 4, 6, 6, 6, 6]

Categories

Resources