Strange behavior of python function [duplicate] - python

This question already has answers here:
Delete from a list while iterating [duplicate]
(3 answers)
Closed 6 years ago.
I'm in a online course at edx of Python and I have to do this little program, I think that the function is right but it has the bug when a element is deleted from the List suddenly the next element is not consider into the test.
def f(i):
return i + 2
def g(i):
return i > 5
def applyF_filterG(L, f, g):
"""
Assumes L is a list of integers
Assume functions f and g are defined for you.
f takes in an integer, applies a function, returns another integer
g takes in an integer, applies a Boolean function,
returns either True or False
Mutates L such that, for each element i originally in L, L contains
i if g(f(i)) returns True, and no other elements
Returns the largest element in the mutated L or -1 if the list is empty
"""
# Your code here
i = 0
if len(L) == 0:
return -1
while i < len(L):
if not g(f(L[i])):
del L[i]
i += 1
return max(L)
If I try with this example L = [0, -10, 5, 6, -4, -2], the value of L should be L = [5,6] but the result is this [-10, 5, 6, -2] the element -10 is skipped when the 0 was deleted and the same happens with -4 and -2. Please help, I don't know how can solve this.

Try not to iterate through the list that you mutate inside the loop in Python. In this example, the index order changed after you delete the element. I created a copy of L before iterating it and it does the trick.
def applyF_filterG(L, f, g):
copied_L = L[:]
for i in copied_L:
if not g(f(i)):
L.remove(i)
if len(L)==0:
return -1
else:
return max(L)

The problem:
You're deleting elements from the list as you iterate over the list. Due to that, i no longer refers to the right element in the list.
To illustrate this problem, here's a sample run through your code.
For this example we are going to assume that the if statement deletes an element if it's value is even.
I also assume that i has already been initialized.
L = [1, 2, 6, 3, 4]
iteration 1
i == 0, L[i] == 1, we don't delete the element.
L == [1, 2, 6, 3, 4]
iteration 2
i == 1, L[i] == 2, element is deleted.
L == [1, 6, 3, 4]
iteration 3
i == 2, L[i] == 3, we don't delete the element.
L == [1, 6, 3, 4]
# Did you notice that we just skipped checking the 6 because its index moved?!
iteration 4
i == 3, L[i] == 4, element is deleted.
L == [1, 6, 3]
We're finished!
There's a couple of ways to do this. Although #Meerness already supplied one way to do it, here's another way you could do it just for completeness.
i = len(L) - 1
if i == -1:
return -1
while i >= 0:
if not g(f(L[i])):
del L[i]
i -= 1
How this works:
In this way of doing it, you count down from the topmost index. That way, the deletion of an element doesn't affect the indexes of elements that you still haven't checked.
My explanation of this way of doing it is a slightly reworded version of the comment by #JohnColeman.
JSYK, though, I had already written this solution before I saw his comment so I didn't borrow the idea from him -- I only borrowed his explanation. :)
Here's an example of what happens when we count down instead of counting up:
L = [1, 2, 6, 3, 4]
iteration 1
i == 4, L[i] == 4, element is deleted.
L == [1, 2, 6, 3]
iteration 2
i == 3, L[i] == 3, we don't delete the element.
L == [1, 2, 6, 3]
iteration 3
i == 2, L[i] == 6, element is deleted.
L == [1, 2, 3]
iteration 4
i == 1, L[i] == 2, element is deleted.
L == [1, 3]
iteration 5
i == 0, L[i] == 1, we don't delete the element.
L == [1, 3]
We're finished!
PS: Examples automatically generated with python3 script. :)

Mutating a list as you're iterating over it is a bad idea and will lead to unexpected behaviour. In general, you're better off creating a new list to work on.
In this specific case, your code can be fixed with an easy modification. Only iterate the index if you aren't deleting an element, so as not to skip ahead in the list.
while i < len(L):
if not g(f(L[i])):
del L[i]
else:
i += 1

def applyF_filterG(L, f, g):
"""
Assumes L is a list of integers
Assume functions f and g are defined for you.
f takes in an integer, applies a function, returns another integer
g takes in an integer, applies a Boolean function,
returns either True or False
Mutates L such that, for each element i originally in L, L contains
i if g(f(i)) returns True, and no other elements
Returns the largest element in the mutated L or -1 if the list is empty
"""
M =[]
for i in range(len(L)):
if g(f(L[i])):
M.append(L[i])
L = M[:]
if len(L)==0:
return -1
else:
return max(L)

Related

How can I use recursion to add two lists together?

For example, say I have
a = [1, 3, 9]
b = [1, 2, 4, 5]
and I want to return a new list(n) in sequential order:
n = [1,1,2,3,4,5,9] by using recursion.
I have tried doing this using a standard recursion method but my issue is that whenever I create a new list inside the recursive function, its value is always set to [] again during the recursive call. Is there any way to have n not go back to being empty?
def recur(a,b):
if a[0] <= b[0]:
n.append(a[0])
return (merge(a[1:], b))
One way (off the top of my head, may have mistakes):
def merge_sorted(a, b):
if not a:
return b
if not b
return a
if a[0] < b[0]:
return [a[0]] + merge_sorted(a[1:], b)
return [b[0]] + merge_sorted(a, b[1:])
I suggest implementing a few versions, and try finding some yourself, if learning recursion is your goal. One version could be similar to what you started - returning the result in a 3d list (so you would need a 3d argument. The other option is a global which i don't like personally). The recipe for me is always the same:
Define the stop condition. In the above, obviously if one of the inputs is empty, and the other is sorted, than the other one is indeed the result.
From that infer the returning invariant - in the above case is that the result of merge_sorted returns a proper merger of the lists.
Given the invariant, construct the recursive call reducing the size of the input.
This is very similar to proof by induction (identical?).
You can do this:
def recur(array):
ret = []
if len(array) <= 1:
return array;
half = int(len(array) / 2)
lower = recur(array[:half])
upper = recur(array[half:])
lower_len = len(lower)
upper_len = len(upper)
i = 0
j = 0
while i != lower_len or j != upper_len:
if( i != lower_len and (j == upper_len or lower[i] < upper[j])):
ret.append(lower[i])
i += 1
else:
ret.append(upper[j])
j += 1
return ret
a = [1, 3, 9]
b = [1, 2, 4, 5]
print(a+b) #[1, 3, 9, 1, 2, 4, 5]
print(recur(a+b)) #[1, 1, 2, 3, 4, 5, 9]
a = [1, 3, 9]
b = [1, 2, 4, 5]
c=[]
def recurse(a,b,c):
if len(a)==0 and len(b)==0:
pass
elif len(a)==0 and len(b)!=0:
c.extend(b)
elif len(a)!=0 and len(b)==0:
c.extend(a)
elif a[0]<b[0]:
c.append(a[0])
del a[0]
recurse(a,b,c)
else:
c.append(b[0])
del b[0]
recurse(a,b,c)
recurse(a,b,c)
Explanation:
1.Create an empty list c
2.1st If: if both 'a' & 'b' are empty, your recursion is complete
3.1st elif:If only 'a' is empty, extend() all value of 'b' to 'c' & recursion complete
4.2nd elif: similar to 1st elif but vice versa
5.If both are non-empty, check for 1st element of 'a' & 'b'. Whichever is lower, append to 'c' & delete, recursively call 'recurse'
Though it considers both 'a' & 'b' are sorted as in the example above

Unable to reset counters in for loop

I am trying to amend a list of integers in a way that every 2 duplicating integers will be multiplied by 2 and will replace the duplicates. here is an example:
a = [1, 1, 2, 3] = [2, 2 ,3] = [4 ,3]
also : b = [2, 3, 3, 6 ,9] = [2 , 6 , 6, 9] = [2, 12 , 9]
I am using the code below to achieve this. Unfortunately, every time I find a match my index would skip the next match.
user_input = [int(a) for a in input().split()]
for index, item in enumerate(user_input):
while len(user_input)-2 >= index:
if item == user_input[index + 1]:
del user_input[index]
del user_input[index]
item += item
user_input.insert(index,item)
break
print(*user_input)
In Python, you should never modify a container object while you are iterating over it. There are some exceptions if you know what you are doing, but you certainly should not change the size of the container object. That is what you are trying to do and that is why it fails.
Instead, use a different approach. Iterate over the list but construct a new list. Modify that new list as needed. Here is code that does what you want. This builds a new list named new_list and either changes the last item(s) in that list or appends a new item. The original list is never changed.
user_input = [int(a) for a in input().split()]
new_list = []
for item in user_input:
while new_list and (item == new_list[-1]):
new_list.pop()
item *= 2
new_list.append(item)
print(*new_list)
This code passes the two examples you gave. It also passes the example [8, 4, 2, 1, 1, 7] which should result in [16, 7]. My previous version did not pass that last test but this new version does.
Check if this works Rory!
import copy
user_input = [1,1,2,3]
res = []
while res!=user_input:
a = user_input.pop(0)
if len(user_input)!=0
b = user_input.pop(0)
if a==b:
user_input.insert(0,a+b)
else:
res.append(a)
user_input.insert(0,b)
else:
res.append(a)
user_input = copy.deepcopy(res)
You can use itertools.groupby and a recursion:
Check for same consecutive elements:
def same_consec(lst):
return any(len(list(g)) > 1 for _, g in groupby(lst))
Replace consecutive same elements:
def replace_consec(lst):
if same_consec(lst):
lst = [k * 2 if len(list(g)) > 1 else k for k, g in groupby(lst)]
return replace_consec(lst)
else:
return lst
Usage:
>>> a = [8, 4, 2, 1, 1, 7]
>>> replace_consec(a)
[16, 7]

How do I stop the function when I have a unique list?

I tried a function that would remove both adjacent duplicates in a list. The remove any new duplicate pair and the function will keep going until there are no more duplicate pairs in the list.
I ran into the issue of figuring out how to tell the function to stop once I have a list without adjacent duplicates.
def removepair(no):
i = 1
if len(no) == 0 or len(no) == 1:
return no
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
return removepair(no)
So far the function will return 0 or single elements after removal:
input: [1, 2, 2, 1] output: []
or
input: [4, 4, 4, 4, 4] output: [4]
But the problem is I don't know how to stop the recursive function once it has a list with more than 1 element:
input: [1,2,3,3,2,1,5,6,7]
expected output: [5,6,7]
We may be able to avoid boolean flags and counters if we set up our recursion carefully:
def removepairs(numbers):
if not numbers: # base case #1, empty
return numbers
first, *second_on = numbers
if not second_on: # base case #2, one element
return numbers
second, *third_on = second_on
if first == second:
return removepairs(third_on)
result = [first] + removepairs(second_on)
if result == numbers:
return numbers # base case #3, no change!
return removepairs(result)
print(removepairs([1, 2, 3, 3, 2, 1, 5, 6, 7]))
OUTPUT
> python3 test.py
[5, 6, 7]
>
If recursive function is not a requirement, it can be simply done using the following code. I have commented the print statement.
def removepair(input_list):
unique_input_list = list(set(input_list))
output_list = list(x for x in unique_input_list if input_list.count(x)%2 == 1)
#print('Input List: ', input_list)
#print('Output list: ', output_list)
return output_list
Input List: [1, 2, 3, 3, 2, 1, 5, 6, 7]
Output list: [5, 6, 7]
Input List: [4, 4, 4, 4, 4]
Output list: [4]
Input List: [1, 2, 3, 3, 2, 1]
Output list: []
Your recursion should stop when no elements where popped from the list, not when the list is almost empty:
def removepair(no):
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) < L:
# elements where popped since the list len has decreased
return removepair(no)
else:
return no
Your code is difficult to understand since it uses a mix of recursion and side effects. Usually, you use either one or the other. Here you can replace your recursive call with a while:
def removepair(no):
while True:
L = len(no)
if L <= 1:
return no
i = 1
while i < len(no):
if no[i] == no[i-1]:
no.pop(i)
no.pop(i-1)
i -= 1
i += 1
if len(no) == L: # no elements where popped
return no
But it's not really Pythonic and I think you should not modify the parameter no inside the function but rather return a new list. Why not iterate over the list and do not copy the duplicates in the result?
def removepair(no):
ret = []
for e in no:
if ret and e == ret[-1]: # current element is the same as the last element
ret.pop()
else:
ret.append(e)
return ret
Or with a fold:
def removepair(no):
import functools
return functools.reduce(lambda acc, x: acc[:-1] if acc and acc[-1]==x else acc+[x], no, [])

how to fix the sort function

def merge (l1,l2):
if l1 and l2:
if l1 == [] and l2 == []:
return []
if l1[0] > l2[0]:
l1, l2 = l2, l1
return [l1[0]] + merge(l1[1:], l2)
return l1 + l2
def sort(l):
x = len(l) / 2
x = int(x)
y = merge(l[0:x], l[x+1:])
return y
I need to write a recursive function named sort; it is passed any unordered list (all int or all str) and it returns a new list that contains every value from its argument list, but in sorted/non-descending order. But I cannot call any of Python’s functions/methods that perform sorting.
also, For any list that has at least 2 values, I have to break the list in half and recursively call sort to sort each smaller list, I have to use the merge function, written above, to merge these two sorted lists returned from these recursive calls
merge is a function to combine and sort two list
merge([1,3,5,8,12],[2,3,6,7,10,15]) returns [1,2,3,3,5,6,7,8,10,12,15].
For example, calling sort([4,5,3,1,6,7,2]) would call sort recursively on the lists [4,5,3] and [1,6,7,2]), returning the lists [3,4,5] and [1,2,6,7] respectively, which when merged would return the list [1,2,3,4,5,6,7].
My function got the following error
39 *Error: sort([1,2,3,4,5,6,7]) -> [1, 2, 3, 5, 6, 7] but should -> [1, 2, 3, 4, 5, 6, 7]
40 *Error: sort([7,6,5,4,3,2,1]) -> [3, 2, 1, 7, 6, 5] but should -> [1, 2, 3, 4, 5, 6, 7]
41 *Error: sort([4,5,3,1,2,7,6]) -> [2, 4, 5, 3, 7, 6] but should -> [1, 2, 3, 4, 5, 6, 7]
42 *Error: sort([1,7,2,6,3,5,4]) -> [1, 3, 5, 4, 7, 2] but should -> [1, 2, 3, 4, 5, 6, 7]
What is wrong with me sort method? can someone help me to fix it? thanks in advance.
Three problems:
Your y = merge(l[0:x], l[x+1:]) loses l[x], make it y = merge(l[:x], l[x:]).
It doesn't sort the halves, so make it y = merge(sort(l[:x]), sort(l[x:])).
You have no base case, stopping the recursion when there's nothing to do.
Corrected and simplified a bit:
def sort(l):
if len(l) <= 1:
return l
x = len(l) // 2
return merge(sort(l[:x]), sort(l[x:]))
The // is integer division so you don't need the extra int(...). And no point in creating that y variable.
Btw, the if l1 == [] and l2 == []: test inside if l1 and l2: is pointless (if l1 and l2 were [], you wouldn't get inside the if l1 and l2: block in the first place), so you can remove it.
One more thing: While your merge function isn't wrong, it's slow. Every l1[1:] takes time proportional to the length of l1. You'd better uses indexes, like for example in Huy Vo's answer.
Ok basically everything you're doing is redundant.
list1 = [1,3,5,8,12]
list2 = [2,3,6,7,10,15]
list3 = list1 + list2 # Merges lists
list3_sorted = sorted(list3) # Sorts them
Also a little bonus, if you have a list of lists or tuples and you want to sort by an index of each of those
from operator import itemgetter
list = [(2,6), (3,4)]
list_sorted = sorted( list, key=itemgetter(1) ) # Will sort by index 1 of each item.
Edit: I now realise that you can't use any built in functions, give me a little to mess around and see if I can figure something out
What you need is a merge sort, I believe there are multiple merge sort pseudocodes on the internet.
Anyway, here is a version of mine in Python 3:
def mergesort(lst):
if len(lst) < 2:
return lst
else:
middle = len(lst) // 2
# recursion, baby
left_half = mergesort(lst[:middle])
right_half = mergesort(lst[middle:])
return merge(left_half, right_half)
def merge(left, right):
result = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
elif left[i] > right[j]:
result.append(right[j])
j += 1
result += left[i:] + right[j:]
return result

Return lists that do not have 1s

I want to create what I thought was a fairly straightforward function. The function just runs through a list of lists and returns any list that does not have a 1 in all of the list elements following the second element ([2: ]). So given the list of lists [[1, 2, 1, 1, 1, 1], [4, 5, 1, 2, 0.3, 1, 1, 1]] the function would return [4, 5, 1, 2, 0.3, 1, 1, 1]. What I have so far is:
def discover(A):
"""Looks for list that has an element not equal to one.
"""
for i in range(len(A)):
for j in range(len(A[i])):
if A[i][j+2] != 1:
print A[i]
But when I run the function it finds one list but then prints that list over and over again before giving me an IndexError saying the list index is out of range. This seems to be a fairly easy problem but for some reason I'm not getting it. Any help would be really appreciated.
The problem is these two lines:
for j in range(len(A[i])):
if A[i][j+2] != 1:
What'll happen is that you'll eventually get to a point where j is the length of your list, minus 1. But then you're calling j+2 in the below code, and that's guaranteed to create a number longer than your list, giving you the IndexError. You can fix that with:
for j in range(2,len(A[i])):
if A[i][j] != 1:
As for the endless printing, you're nearly there, but you'll want to stop the loop if you find the non-1 element.
if A[i][j] != 1:
print A[i]
break
Alternately, the other answers will give you the same result more easily. But that's where your current errors are coming from.
for list in A:
if 1 not in list[3:]:
print list
even another solution:
lst = [
[1,2,3],
[1,1,1],
[3,4,5],
[3,5,6],
] # +++
def has1(subLst):
return subLst.count(1) == 0
print filter(has1, lst)
This avoids out of range issues.
def discover(A):
results = []
for lst in A:
for i in lst[3:]:
if i != 1:
results.append(lst)
break
return results
In addition to the other answers here, one could also make use of a generator. The yield statement will allow you to skirt establishing a default list to place your results into; you can just specify the condition you're looking for and yield the result.
>>> def discover(lists):
... for l in lists:
... if not [x for x in l[2:] if x != 1]:
... yield l
>>> stuff = [[2, 3, 4, 5, 1, 2], [2, 5, 1, 1, 1, 1, 1]]
>>> results = discover(stuff) #returns <generator object discover at 0x105e3eb90>
>>> results.next()
[2, 5, 1, 1, 1, 1, 1]
>>>
The magic line here being, if not [x for x in l[2:] if x !=1]. It builds a list from l[2:] and checks that any variable in there does not equal 1; if the list has no length, it means there are no non-1 entries in l[2:] and so it yields l.
A query to check if any element (after the second) != 1 would be:
any(x != 1 for x in mylist[3:])
so
def discover(A):
for mylist in A:
if any(x != 1 for x in mylist[3:]):
print mylist

Categories

Resources