Related
This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed last year.
I've been given a list of integers called nums, and am trying to remove all occurrences of the value (val). I'm trying to see where val matches an index of nums and am trying to remove it from the list. However, I keep getting a "list index out of range" error. I'm guessing it is because as I am popping the element of nums that matches val, it shrinks the list so it goes out of range. Is it not possible to remove all occurrences of value in this way?
nums = [3,2,2,3]
val = 2
for i in range(len(nums)):
if val == nums[i]:
nums.pop(i)
print(nums)
You should not try to remove elements from the list as you're iterating over it, as the memory is shifting as you're accessing it.
Instead, you should create a new list:
nums = [3,2,2,3]
val = 2
print([num for num in nums if val != num]) # Prints [3, 3]
use remove method to remove vlaues from list
nums = [3,2,2,3]
val = 2
l = list(nums)#creating new list
for v in l:
if v == val:
nums.remove(val)#removing values
print(nums)
output:
$ python3 file.py
[3, 3]
This question already has answers here:
Finding first and last index of some value in a list in Python
(9 answers)
Closed 2 years ago.
for example, there is a list:
numbers = [1,2,3,4,2,1]
print(numbers.index(1))
output is
0
element behind is preferred means that, the index of the "1" at the end should be printed instead of the the "1" at the beginning.
i.e.
output should be 5
A baseline method of "reverse the list" is provided bellow, it there a faster & simpler way to do this?
numbers = [1,2,3,4,2,1]
numbers.reverse()
output = []
for i in range(1,4):
inv_inx= len(numbers)-numbers.index(i)-1
output.append(inv_inx)
print(output)
assert output == [5, 4, 2]
An approach that doesn't make any temporary lists, scans from the end (so it doesn't find all the 1s you don't care about) and short-circuits (so it stops when the element is found) is to combine a generator expression with the next, reversed and enumerate built-ins:
def rindex(seq, value):
return len(seq) - next(i for i, x in enumerate(reversed(seq), 1) if x == value)
This will raise StopIteration if the value is not found, if you want it to raise ValueError, you just have to convert it:
def rindex(seq, value):
try:
return len(seq) - next(i for i, x in enumerate(reversed(seq), 1) if x == value)
except StopIteration:
raise ValueError(f"{value!r} is not in {type(seq).__name__}") # Rough equivalent to list.index message
For comparison, a version without a generator expression or next would look like this:
def rindex(seq, value):
for i, x in enumerate(reversed(seq), 1):
if x == value:
return len(seq) - i
raise ValueError(f"{value!r} is not in {type(seq).__name__}")
You can start the search from the end of the list:
The list index() method can take a maximum of three arguments:
element - the element to be searched
start (optional) - start searching from this index
end (optional) - search the element up to this index
numbers = [1,2,3,4,2,1]
print(numbers.index(1,-1))
OUTPUT:
5
EDIT:
If the search item is not in the end of the list:
lst = [1,2,3,4,2,1,2]
print(len(lst)-1 - lst[::-1].index(1)) # 5
Source
try this not the best solution but better.
numbers = [1,2,1,4,2,1]
count=0
for i in numbers :
count=count+1
if i == 1:
a=[]
a.append(count-1)
print(a) #5
The objective of this function is to remove the first two occurrences of n in a list.
Below is a code I had written but I still got it wrong after many hours. A friend advised me not to edit a list while iterating. However, I'm still stuck.
def remove_first_two(list,n):
if list == []:
return []
else:
count = 0
for ele in list:
if ele == n:
list.remove(ele)
count += 1
if count == 2:
break
return list
list = [1,2,2,3]
print(remove_first_two(list,2)) => [1,2,3] instead of [1,3]
Use list.remove twice with try-except. That will delete first two entries. Complexity O(n)
list_a = [1,2,3,4]
try:
list_a.remove(n)
list_a.remove(n)
# run a loop too, if it's more than 2
except:
pass
You can try find all indexes and del:
a = [1,2,3,2,3,2,4]
indices = [i for i, x in enumerate(a) if x == 2]
print(indices)
[1, 3, 5]
del a[indices[0]], a[indices[1]]
print(a)
[1, 3, 2, 2, 4]
First, don't use 'list' as its a key word in Python. Use something else, like 'alist'.
The code below does what you want and keeps the basic form of what you already have. You can of course also use the built-in .remove() method.
def remove_first_two(alist, n):
if alist == []:
return []
else:
count = 0
while count < 2:
for ele in alist:
if ele == n:
alist.remove(ele)
count += 1
return alist
alist = [1,2,2,3]
print(remove_first_two(alist,2)) # Output -> [1,3]
When your friend says "do not edit a list while iterating," he/she is right, and what he/she means is that you should create another list all together. What you are looking to do is the following:
def remove_first_two(list, n):
if list == []:
return []
else:
new_list = []
count = 0
for ele in list:
if ele == n:
if count >= 2:
new_list.append(ele)
count += 1
else:
new_list.append(ele)
return new_list
However, note that you can use use some built in functions to make your life much easier:
list.remove(x)
Remove the first item from the list whose value is equal to x. It raises a ValueError if there is no such item.
Therefore, you can more simply do:
def remove_first_two(list, n):
if list == []:
return []
for _ in range(2):
if n in list:
list.remove(n)
return list
Python updates the list if you change it while iterating.
In you test case with list = [1,2,2,3] when list[1] is deleted and Python updates list = [1,2,3]. Now Python understands you have iterated till index 1 and continues from index 2 which now contains 3. So Python encounters only one occurance of 2.
So heed your friends advice and do not edit list while iterating :)
Now you can use Python's in-built list.remove(element) to delete first ocuurence of a element. Repeat it 2 times for desired output.
Also O(n) with a single parse.
def remove_first_two(mylist,n):
counter = 0
def myfilter (i):
nonlocal counter,n
if counter > 2:
return True
else:
counter += 1
return (i != n)
return (list(filter(myfilter,mylist)))
This can also be done in python 3.8 using assignment expressions in a list comprehension:
data = [1,2,3,2,3,2,4]
count = 2
num = 2
[x for x in data if x != num or (count:=count-1) < 0]
Results:
[1, 3, 2, 2, 4]
Here is the reason why your program does not work:
When you remove an element, the for loop moves on to the next element, but by "moving on" it is actually skipping the element which now occupies the position of the deleted element. It skips the element right after the one you deleted.
The correct way to iterate over a list while you delete elements is making index progression explicit, by using a while loop instead of a for loop, and not increase the index when you delete an element:
i = 0
while i < len(my_list):
if condition:
my_list.pop(i)
else:
i += 1
However, none of this is necessary in your case! Notice that when you use my_list.remove(ele), you are not providing an index as you would with my_list.pop(i), so Python has to search for the first element that matches ele. Although remove will be slower than pop when used by themselves, here remove allows you not use any loops at all, simply do my_list.remove(n) twice!
Last touch: If your list has less than two elements matching n, one of the two my_list.remove(n) commands would return a ValueError. You can account for this exception, knowing that if it happens, your list is ready and requires no further action.
So the code you need is:
try:
my_list.remove(n)
my_list.remove(n)
except ValueError:
pass
How can I resolve this IndexError? I tried by using a while loop, but nothing changed.
Here is my code, it should check the length of the object of two lists (la, lb) and remove the string from la if the string is shorter than the lb string and vice versa. Plus it has to remove both of the strings if their length is the same.
def change(l1, l2):
la1 = l1[:]
la2 = l2[:]
i = 0
for i in range(len(la1)):
if la1[i] == la2[i]:
l1.pop(i)
l2.pop(i)
elif la1[i] > la2[i]:
l2.pop(i)
elif la2[i] > la1[i]:
l1.pop(i)
Assuming your lists are of equal lengths
As has been pointed out in the comments, the IndexError happens due to your lists' length changing when you pop() an item.
Since you're iterating over your list using a range(len(l)) in a for loop, which isn't updated after every completed loop, you'll eventually hit an index that's out of range.
An example, which you can try easily enough yourself:
l = [1,2,3,4,5,6,7,8,9,10]
for i in range(len(l)):
l.pop(i)
print("Length of list", len(l))
Do not confuse yourself by calling print(range(len(l)) in the for loop - this will give you an updated range, but is misleading. The range in the for loop is only called once, hence never updates while iterating.
A different approach
Instead of working with indices, try using zip() and building new lists, instead of changing existing ones.
def change(l1, l2):
new_l1 = []
new_l2 = []
for a, b in zip(l1, l2):
if len(a) == len(b):
continue # do nothing
elif len(a)<len(b):
new_l2.append(b)
elif len(a)>len(b):
new_l1.append(a)
return new_l1, new_l2
This approach, essentially, generates the same list you create using pop(), while avoiding usage of indices.
Note that zip() will stop once it reaches the end of the smaller of both iterables. If your lists may not be of equal length, and you'd like to iterate until the longest of both iterables is iterated over entirely, use zip_longest(). But I do not think this is what you need in this case.
Additional Notes
You would also run into a problem if you were to iterate over your list using the following code:
l = [i for i in range(10)]
for item in l:
l.remove(item)
>>>[1, 3, 5, 7, 9]
Essentially, it's not advisable to iterate over any iterable while changing it. This can result in anything from an Exception being thrown, to silent unexpected behaviour.
I'm aware you were avoiding this by looping over the copies, I just wanted to add this for posterity.
You can traverse the lists backwards, so that when you remove an item from the list the indices of the elements that you have not examined yet won't be affected
def f(a, b):
l = len(a) if len(a)<len(b) else len(b)
for i in range(l):
j = l-i-1
la, lb = len(a[j]), len(b[j])
if la<lb: a.pop(j)
elif lb<la: b.pop(j)
else: a.pop(j), b.pop(j)
return a, b
ps I staid faithful to your problem statement and not to your implementation re the comparison based on strings' lengths.
if you want to iterate over a list and want to empty it
but don't want pop index error use this:
lst = [ 1, 4, 56, 2, 4 , 12, 6, 89 ,11, 0]
i =0
while len(lst) != 0:
lst.pop(0)
i+=1
print(lst)
I looked up and found a close example, but the answer found in this link: Remove adjacent duplicate elements from a list won't run the test cases for this problem. So this is all I have so far:
def remove_dups(thelist):
"""Returns: a COPY of thelist with adjacent duplicates removed.
Example: for thelist = [1,2,2,3,3,3,4,5,1,1,1],
the answer is [1,2,3,4,5,1]
Precondition: thelist is a list of ints"""
i = 1
if len(thelist) == 0:
return []
elif len(thelist) == 1:
return thelist
elif thelist[i] == thelist[i-1]:
del thelist[i]
return remove_dups(thelist[i:])
def test_remove_dups():
assert_equals([], remove_dups([]))
assert_equals([3], remove_dups([3,3]))
assert_equals([4], remove_dups([4]))
assert_equals([5], remove_dups([5, 5]))
assert_equals([1,2,3,4,5,1], remove_dups([1,2,2,3,3,3,4,5,1,1,1]))
# test for whether the code is really returning a copy of the original list
mylist = [3]
assert_equals(False, mylist is remove_dups(mylist))
EDIT while I do understand that the accepted answer linked above using itertools.groupby would work, I think it wouldn't teach me what's wrong with my code & and would defeat the purpose of the exercise if I imported grouby from itertools.
from itertools import groupby
def remove_dups(lst):
return [k for k,items in groupby(lst)]
If you really want a recursive solution, I would suggest something like
def remove_dups(lst):
if lst:
firstval = lst[0]
# find lowest index of val != firstval
for index, value in enumerate(lst):
if value != firstval:
return [firstval] + remove_dups(lst[index:])
# no such value found
return [firstval]
else:
# empty list
return []
Your assertion fails, because in
return thelist
you are returning the same list, and not a copy as specified in the comments.
Try:
return thelist[:]
When using recursion with list it is most of the time a problem of returning a sub-list or part of that list. Which makes the termination case testing for an empty list. And then you have the two cases:
The current value is different from the last one we saw so we want to keep it
The current value is the same as the last one we saw so we discard it and keep iterating on the "rest" of the values.
Which translate in this code:
l = [1,2,2,3,3,3,4,5,1,1,1]
def dedup(values, uniq):
# The list of values is empty our work here is done
if not values:
return uniq
# We add a value in 'uniq' for two reasons:
# 1/ it is empty and we need to start somewhere
# 2/ it is different from the last value that was added
if not uniq or values[0] != uniq[-1]:
uniq.append(values.pop(0))
return dedup(values, uniq)
# We just added the exact same value so we remove it from 'values' and
# move to the next iteration
return dedup(values[1:], uniq)
print dedup(l, []) # output: [1, 2, 3, 4, 5, 1]
problem is with your return statement,
you are returning
return remove_dups(thelist[i:])
output will be always last n single element of list
like for above soon,
print remove_dups([1,2,2,3,3,3,4,5,1,1,1])
>>> [1] #as your desired is [1,2,3,4,5,1]
which returns finally a list of single element as it don't consider Oth element.
here is recursive solution.
def remove_dups(lst):
if len(lst)>1:
if lst[0] != lst[1]:
return [lst[0]] + remove_dups(lst[1:])
del lst[1]
return remove_dups(lst)
else:
return lst