delete elements from list in python and avoid shifting [duplicate] - python

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 3 years ago.
I have a list in python and every time an element satisfies a certain condition I remove the element. The problem is that the for cycle seems to be skipping some elements. I think it's because the list is moved to the left after a delete. So how to properly remove items in a list? This is my code
list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]
for elem in list:
if elem == 0:
list.remove(elem)
print(list)
and this is the result [1, 1, 0, 1, 0]

You shouldn't delete or add elements to a list whilst iterating over it. Here are some alternatives.
Method 1: Create a new filtered list.
[x for x in my_list if x != 0]
Method 2: If the items are booleans, you can use filter.
list(filter(lambda x: x, my_list)) # or lambda x: x != 0 depending on intent.
Method 3: Create an index of items to be deleted and then subsequently remove them.
idx = [n for n, x in enumerate(my_list) if x == 0]
for i in reversed(idx):
my_list.pop(i)
Method 4: Per the solution of Alex Martelli in the related question
my_list[:] = [x for x in my_list if x != 0]
This assigns to a list slice that matches the my_list variable name, thereby preserving any existing references to this variable.

You can't use .remove() because that removes the first occurrence of the given value, but you need to remove items by their position in the list.
Also you'll want to iterate the positions in reverse order, so removing an item doesn't shift the positions of the remaining (earlier) list items.
for position in reversed(range(len(mylist))):
if mylist[position] == 0:
del mylist[position]

use filter
lst = [0, 0, 0, 1, 1, 0 ,0, 1, 0]
lst = list(filter(lambda elem: elem != 0, lst))
print(lst)
also avoid defining the term list as this is already being used by the built in list type, otherwise you'll prevent yourself from using terms like list(...) inside of this scope.
alternatively you can use a list comprehension
lst = [0, 0, 0, 1, 1, 0 ,0, 1, 0]
lst = [elem for elem in lst if elem != 0]
print(lst)

There are more 'pythonic' ways to solve this problem using a lambda function. But to solve this using just looping it becomes easier to understand:
list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]
i = 0
length = len(list)
while (i < len(list)):
if list[i] == 0:
list.remove(list[i])
i -= 1
length -= 1
else:
i += 1
print(list)
By decreasing i by 1 inside the if statement we can ensure we don't miss any elements. Also to prevent out of bounds looping we must also decrease the length used in the while condition.

You can use this if you always want to remove 0's from your list:
[elem for elem in list if elem]
This obviously gives you a new list without making changes to your existing list.

As you said, when the item is deleted, the list moves. Since the for loop uses the index within the loop, it gets the index wrong and the wrong item gets removed.
A simple way around this, if you don't wish to rewrite a lot of code is to traverse the list backwards
list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]
for elem in reversed(list):
if elem == 0:
list.remove(elem)
print(list)

Related

Optimise a loop that modifies a list [duplicate]

This question already has answers here:
How to find the last occurrence of an item in a Python list
(15 answers)
Closed 2 years ago.
I've got this piece of code that modifies a list based on a condition:
lst = [0, 0, 0, 2, 1]
for i, x in enumerate(reversed(lst)):
if x == 0:
lst[-i-1] = 9
break
Is there a way I can do this in a better, more optimised way and without a while loop?
If the list starts with a sequence of 0's, and there are no other 0's in it, you can use list.count to find the position of the final 0 (it will be one less than the count of 0's in the list) and replace it with 9:
lst = [0, 0, 0, 2, 1]
lst[lst.count(0)-1] = 9
print(lst)
Output:
[0, 0, 9, 2, 1]
It appears you just want the starting lst modified by replacing the first element from the end that's a 0 with a 9.
How about this:
lst = [0, 0, 0, 2, 1]
lst[-(list(reversed(lst)).index(0)+1)] = 9
Sadly, you need the list() in there to have the .index(), since the result from reversed() (a list_reverseiterator) doesn't have that method.
Or if you're all about brevity:
lst[-lst[::-1].index(0)-1] = 9
I don't think that helps readability though. (Edit: removed initial -1::-1, the first -1 is superfluous as #Nick correctly pointed out)

Trying to compare two lists, find the equal values and replace them with the index of the other list, but it is not working

Basically, I have a list that contains all the possibles values of the second list. For example:
First list (Possible values):
list1 = ['cat','dog','pig']
Second list:
list2 = ['dog','cat','cat','cat','dog','pig','cat','pig']
I want to compare those lists and substitute all the strings in the second list to the index of the first one.
So expect something like this:
list2 = [1,0,0,0,1,2,0,2]
I've tried it in several different ways. The first one, although it worked, was not an intelligent method. Since if the first list had a huge variety of possible values, this strategy would not be functional to code.
That was the first solution:
list3 = []
for i in list2:
if i == 'cat':
i = 0
list3.append(i)
elif i == 'dog':
i = 1
list3.append(i)
elif i == 'pig':
i = 2
list3.append(i)
list2 = list3
print(list2)
output
[1, 0, 0, 0, 1, 2, 0, 2]
But I want a solution that works in a huge variety of possible values without having to code each test.
So I tried this (and other failed attempts), but it isn't working
for i in list2:
for j in list1:
if i == j:
i = list1.index(j)
The problem with your code is that you are simply replacing i on each iteration. You want to create a list and append the result from list1.index(j) to it on each iteration:
l = []
for i in list2:
for j in list1:
if i == j:
l.append(list1.index(j))
Note that this can be simplified with a list comprehension:
[list1.index(i) for i in list2]
# [1, 0, 0, 0, 1, 2, 0, 2]
Note that for a lower complexity solution, you can create a dictionary mapping strings to index, and simply create a list by looking up with the strings in list2, as in #blhshing's answer.
Some reads you might find useful:
Data Structures
List comprehensions
string — Common string operations
The i you get by iterating the list does not reflect changes back into the list. Hence no change.
Create a dictionary that mapps animal to index position.
Use a list comprehension to create a new list by replacing animals of list2 its lookup-index:
list1 = ['cat','dog','pig']
lookup = {animal:index for index,animal in enumerate(list1)}
list2 = ['dog','cat','cat','cat','dog','pig','cat','pig']
result = [lookup.get(what) for what in list2]
print(result) # [1, 0, 0, 0, 1, 2, 0, 2]
Doku:
enumerate(iterable)
Create a dictionary with list comprehension in Python (creation of the lookup)
list comprehension
Why dict.get(key) instead of dict[key]?
You can use a dict comprehension to create a mapping dict that maps keys to indices using the enumerate function, and then map items in list2 to the values of the mapping dict:
mapping = {k: i for i, k in enumerate(list1)}
list(map(mapping.get, list2))
This returns:
[1, 0, 0, 0, 1, 2, 0, 2]
You can use only one for loop and one if-statement that is easy to understand.
list1 = ['cat','dog','pig']
list2 = ['dog','cat','cat','cat','dog','pig','cat','pig']
for i,item in enumerate(list2):
if item in list1:
list2[i] = list1.index(item)
# list2 = [1, 0, 0, 0, 1, 2, 0, 2]

Assigning elements to a lists within a list

I am running Python3.6 and am working with lists which contain other lists within it.
list_array = [[1,0,1,0,2,2],
[1,1,2,0,1,2],
[2,2,2,1,0,1]]
I would like to modify the list called list_array be deleting all the entries with value 2 within the sub lists.
The code I used for this is
for k in list_array:
k = [x for x in k if x!=2]
However, this code doesn't modify list_array.
Why isn't it possible to replace the elements in the lists within list_array this way?
You are creating a new list instead of assigning to the old one. You can fix this by adding an assignment using k[:] =, like this:
for k in list_array:
k[:] = [x for x in k if x!=2]
Your code creates a new list every time, and erase the previous one.
At the last iteration you should get this:
k = [1, 0, 1]
Instead, a list comprehension works fine:
list_array = [[x for x in sublist if x != 2] for sublist in list_array]
Output:
[[1, 0, 1, 0], [1, 1, 0, 1], [1, 0, 1]]
If you want to write it with an explicit for loop, it could be done like this:
new_list_array = list()
for sublist in list_array:
new_list_array.append([x for x in sublist if x != 2])
You are not modifying the old list you can modify it like that
list_array = [[1,0,1,0,2,2],[1,1,2,0,1,2],[2,2,2,1,0,1]]
for k in list_array:
k[:]= [x for x in k if x!=2]
print(list_array)

How to remove list of list at specific index

I'm trying to figure out how to delete an entire list at a specific index if the first element on the inside list meets a certain condition. Below I've shown an example of what I want done but when I run the code I'm getting a list index out of range error in python. If the list[i][0] meets a certain condition I want that entire list delete from the overall list.
list = [[0, 0, 0], [1, 1, 1], [2, 2, 2]]
for i in range(0, len(list)):
if list[i][0] == 0:
del list[i]
return list
Below I've shown a picture of what happens when I run the sample code in IDLE, the first time I run the loop it gives an error but the second time I run the code (copy and pasted both times) it doesn't and it does what I'm asking.
Weird Python Error
Deleting an element from the middle of a list moves everything down, breaking indexing. You need to either remove elements from the end:
for i in range(len(lst)-1, -1, -1):
if lst[i][0] == 0:
del lst[i]
or build a new list and assign it back to the variable, which would also be much more efficient:
lst = [x for x in lst if x[0] != 0]
Try changing your code to this, and 'list' is not a good variable name since it's already a builtin function:
my_list = [[0, 0, 0], [1, 1, 1], [2, 2, 2]]
my_list = [l for l in list if l[0] != 0]
print(my_list)
Usually it's not a good idea to remove elements from list while iterating through it like that because the first time you remove an element the list will no longer be the same length.
1) Create completely new list which will contain elements you want to keep:
listname = [element for element in listname if element[0] != 0]
2) Modify the list you already have (you can do this since lists are mutable):
listname[:] = [element for element in listname if element[0] != 0]
I would recommend using the second approach in case you have references to the same list somewhere else in you program.
Also try not to name your variables list, it's really not a good practice and it probably is not possible since it's keyword.
First, do not ever call your variables list.
Second, a while loop may be a slightly better solution:
stop = range(len(mylist))
i = 0
while i < stop:
if mylist[i][0] == 0:
del mylist[i]
stop -= 1
else:
i += 1
Third, a list comprehension is event better:
[item for item in mylist if item[0] != 0]
It's always bad idea to remove elements from a container while you are iterating over it.
A better approach would be to instead of removing bad elements from the list, copy good ones to a new list:
original = [[0, 0, 0], [1, 1, 1], [2, 2, 2]]
new_list = []
for l in original:
if list[0] != 0:
new_list.append(l)
return new_list
And by the way, "list" is a python keyword and can't be used as variable name.
Or better, use the built-in filter function:
return filter(lambda l : l[0] != 0, original)

I am trying to remove items from a list based upon items from another list (inside a loop). error: list index out of range

This is my code:
print('oc before remove',oc)
print('remlist before remove', remlist)
for i in range(len(remlist)):
for j in range(len(oc)):
if ( (remlist[i][0] == oc[j][0]) and (remlist[i][1] == oc[j][1]) ):
del oc[j]
print('oc after remove', oc)
'oc' is de list from which I want to remove items that also occur in 'remlist'. My prints output the following:
('oc before remove', [[0, 0, 0]])
('remlist before remove', [[0, 0, 0]])
('oc after remove', [])
('oc before remove', [[1, 0, 1], [0, 1, 1]])
('remlist before remove', [[0, 0, 0], [1, 0, 1]])
Here the error occurs.
So the first time it succeeds, but the second time the following error is given:
IndexError: list index out of range
I understand the meaning of this error, but I don't see why this error occurs here. I use the length of both lists to loop. What is going wrong here?
Your problem is that you change the size of the list during iteration. Which obviously is a problem since after deleting a few items your j loop variable is going to be outside the range of the new (after deletion) list length. The first time it only works because the list only contains 1 element.
Try this instead:
oc = [item for item in oc if item not in remlist]
This list comprehension will keep the items from oc that are not in remlist.
As you have deleted one element from oc during the runtime thats why it will give IndexError: list index out of range, oc[1] element will be missing.
I handel the case using while loop.
>>>oc = [[1, 0, 1], [0, 1, 1]]
>>>remlist = [[0, 0, 0], [1, 0, 1]]
for i in range(len(remlist)):
j = 0
while j <len(oc):
if ( (remlist[i][0] == oc[j][0]) and (remlist[i][1] == oc[j][1]) ):
del oc[j]
j = j-1
j = j+1
Result :
>>>oc
[[0, 1, 1]]
>>>remlist
[[0, 0, 0], [1, 0, 1]]
len(oc) is only evaluated once when you enter the loop, but after one iteration you remove an element and therefor the length of the list is changed. On the next iteration you are trying to access oc[1][0], but at this point oc only has 1 element and therefor throws an exception.
Also note that you only compare the first 2 elements in each element (which in your example contain 3 elements each).
The problem is that you delete items from a list while you are looping over it.
For lists of length 1 this will not give any problem, but anything longer than 1 will give problems, since the list becomes shorter while looping over it.
The thing is, in your second example, that beforehand you tell the loop to go over 2 items (since your list is of length 2). But if you find and remove an item, the list becomes smaller, and it will not be able to loop the full range that you set beforehand. I has become a list of length 1, and therefore you cannot access the second item.
There are two ways:
1) Creating a new list and copying in it the elements that are in oc and not in remlist,
2) Deleting the elements that are in remlist directly from oc (in the case oc is big).
Copying into a new list
res = []
for e in oc:
if not e in remlist:
res.append(e)
Deleting directly from the list
Here, you can use remove.
for e in remlist:
for i in xrange(oc.count(e)): oc.remove(e)
Remark
I do not know why do you compare only the first and second elements of the sub-lists:
if ((remlist[i][0] == oc[j][0]) and (remlist[i][1] == oc[j][1])): ...
It is enough to write:
if (remlist[i] == oc[j]): ...
If you are sure of what you are doing, use at least:
if (remlist[i][0:2] == oc[j][0:2]): ...
It is more pythonic ;)

Categories