It follows that you have two lists with the same number of elements and you want to eliminate the elements in a certain position in both lists when in altura
the value in that position is 0. The first error in this case is that you must go the lists at par, not two for
nested (the second list is covered in full by each element of the first).
Also, you have to take two things into account if you intend to do the in-place removal:
-
Never you must modify the length of a list or any other iterable one (neither add nor add elements) when iterating with a for in
.
-
It's obvious, but keep in mind that when you remove an item from the list, the indexes of the items after it become one less , so you have to consider this. If we use the typical for i in range(len(lista))
we must take into account that the length of the list will change, you indicate that before they were valid at the moment we eliminated an element they are not anymore.
That said, we can do what you want by modifying the lists or creating new objects:
-
In-place:
A possible solution is to iterate over height and create a list that stores the indexes of the elements that are 0. Then we use a for
to iterate over it and with del
remove these indexes from both lists. You have to correct the indexes to take into account the second previous point:
# Metodo 1
filtro = [i-n for i, n in enumerate((i for i, a in enumerate(altura) if not a))]
for i in filtro:
del diametro[i]
del altura[i]
The part i-n for i, n in enumerate
of the list by compression is responsible for correcting the index by subtracting to each index the number of elements previously eliminated when it is reached, it would be equivalent to:
# Metodo 2
filtro = [i for i, a in enumerate(altura) if not a]
e = 0
for i in filtro:
del diametro[i-e]
del altura[i-e]
e += 1
Another possibility is to use range + len
to generate the indexes but iterating from right to left:
# Metodo 3
for i in range(len(altura) - 1, -1, -1):
if not altura[i]:
del altura[i]
del diametro[i]
-
Creating two new lists:
# Metodo 4
filtro = [i for i, a in enumerate(altura) if a]
diametro = [diametro[i] for i in filtro]
altura = [altura[i] for i in filtro]
A different option is to use zip
, although it is somewhat less idiomatic:
# Metodo 5
diametro, altura = (list(l) for l in zip(*((d, a)
for d, a in zip(diametro, altura) if a)))
Finally, the most efficient option I can think of is to use a boolean mask, NumPy style. We can use itertools.compress
to apply it:
# Metodo 6
import itertools
filtro_boleano = [a != 0 for a in altura]
diametro = list(itertools.compress(diametro, filtro_boleano))
altura = list(itertools.compress(altura, filtro_boleano))
Note: list.pop
removes and returns the item in the index that is passed to it. If you are not going to use the eliminated value it is better to use del
for such purposes.
In all cases for example:
diametro = [4, 8, 7, 2, 3, 6, 5, 9, 6, 8]
altura = [0, 0, 6, 0, 1, 0, 3, 4, 8, 0]
We get:
[7, 3, 5, 9, 6]
[6, 1, 3, 4, 8]
IMPORTANT
Removing an item from a list is a costly operation , usually since it involves rearranging the rest of the elements (as is the case with list.insert
). If we do not have memory problems and prioritize the execution time, it may be important to choose to create new lists instead of removing the items directly, especially if we create the lists by compression. Taking this to the extreme, for lists of one million elements, with the elimination of 50% of the items we have the following execution times as reference for the methods shown above in the same order:
Method 1: 97.64361238479614 seconds
Method 2: 304.90778708457947 seconds !!!!
Method 3: 98.10549569129944 seconds
Method 4: 0.1168980598449707 seconds
Method 5: 0.34819936752319336 seconds
Method 6: 0.0786902904510498 seconds
The difference is abysmal and shows how the best method of all is 6 in terms of execution time followed closely by 4, at the expense of using much more memory.