Question Comment supprimer des éléments d'une liste en cours d'itération?


Je fais une itération sur une liste de tuples en Python, et j'essaie de les supprimer s'ils répondent à certains critères.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

Que devrais-je utiliser à la place de code_to_remove_tup? Je ne peux pas comprendre comment enlever l'article de cette façon.


703
2017-07-30 15:36


origine


Réponses:


Vous pouvez utiliser une liste de compréhension pour créer une nouvelle liste contenant uniquement les éléments que vous ne souhaitez pas supprimer:

somelist = [x for x in somelist if not determine(x)]

Ou, en assignant à la tranche somelist[:], vous pouvez modifier la liste existante pour qu'elle ne contienne que les éléments souhaités:

somelist[:] = [x for x in somelist if not determine(x)]

Cette approche pourrait être utile s'il existe d'autres références à somelist qui ont besoin de refléter les changements.

Au lieu d'une compréhension, vous pouvez également utiliser itertools. En Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

Ou en Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

590
2017-07-30 15:41



Les réponses suggérant des compréhensions de liste sont PRESQUE correctes - sauf qu'elles construisent une liste complètement nouvelle et lui donnent le même nom que l'ancienne, car elles ne modifient PAS l'ancienne liste en place. C'est différent de ce que vous feriez par suppression sélective, comme dans la suggestion de @ Lennart - c'est plus rapide, mais si vous accédez à votre liste via plusieurs références, le fait de réappliquer l'une des références et de ne pas modifier l'objet liste lui-même peut conduire à des bugs subtils et désastreux.

Heureusement, il est extrêmement facile d'obtenir à la fois la vitesse de compréhension de la liste ET la sémantique requise de l'altération sur place - il suffit de coder:

somelist[:] = [tup for tup in somelist if determine(tup)]

Notez la différence subtile avec les autres réponses: celle-ci N'APPELLE PAS à un nom de barre - c'est l'assignation à une tranche de liste qui se trouve être la liste entière, remplaçant ainsi la liste Contenu  dans le même objet de liste Python, plutôt que de simplement réattribuer une référence (de l'objet liste précédent à un nouvel objet liste) comme les autres réponses.


491
2017-07-30 19:28



Vous devez prendre une copie de la liste et la parcourir en premier, sinon l'itération échouera avec des résultats inattendus.

Par exemple (dépend de quel type de liste):

for tup in somelist[:]:
    etc....

Un exemple:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

191
2017-07-30 15:38



for i in xrange(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Vous devez revenir en arrière sinon c'est un peu comme scier la branche d'arbre sur laquelle vous êtes assis :-)


76
2017-07-30 15:44



Votre meilleure approche pour un tel exemple serait un liste de compréhension

somelist = [tup for tup in somelist if determine(tup)]

Dans les cas où vous faites quelque chose de plus complexe que d'appeler un determine fonction, je préfère construire une nouvelle liste et y ajouter simplement comme je vais. Par exemple

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

Copier la liste en utilisant remove pourrait rendre votre code un peu plus propre, comme décrit dans l'une des réponses ci-dessous. Vous ne devriez certainement pas le faire pour des listes extrêmement volumineuses, car cela implique d'abord de copier toute la liste, et aussi d'effectuer une O(n)  remove opération pour chaque élément étant enlevé, ce qui en fait un O(n^2) algorithme.

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

45
2017-07-30 15:41



Pour ceux qui aiment la programmation fonctionnelle:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

ou

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

35
2017-07-30 15:46



le tutoriel officiel de Python 2 4.2. "pour les déclarations" dit:

Si vous avez besoin de modifier la séquence que vous êtes en train d'itérer à l'intérieur de la boucle (par exemple pour dupliquer les éléments sélectionnés), il est recommandé de faire une copie. Itérer sur une séquence ne fait pas implicitement de copie. La notation de tranche rend ceci particulièrement pratique:

>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

ce qui a été suggéré à: https://stackoverflow.com/a/1207427/895245

le Documentation Python 2 7.3. "La déclaration pour" donne le même conseil:

Remarque: Il y a une subtilité lorsque la séquence est modifiée par la boucle (cela ne peut se produire que pour des séquences mutables, c'est-à-dire des listes). Un compteur interne est utilisé pour garder trace de l'élément qui est utilisé ensuite, et celui-ci est incrémenté à chaque itération. Lorsque ce compteur a atteint la longueur de la séquence, la boucle se termine. Cela signifie que si la suite supprime l'élément en cours (ou un élément précédent) de la séquence, l'élément suivant sera ignoré (puisqu'il récupère l'index de l'élément en cours qui a déjà été traité). De même, si la suite insère un élément dans la séquence avant l'élément en cours, l'élément en cours sera traité à nouveau la prochaine fois à travers la boucle. Cela peut conduire à des bogues désagréables qui peuvent être évités en faisant une copie temporaire en utilisant une tranche de la séquence entière, par exemple,

for x in a[:]:
    if x < 0: a.remove(x)

Python pourrait-il faire mieux?

Il semble que cette API Python particulière pourrait être améliorée. Comparez-le, par exemple, avec son homologue Java ListIterator, ce qui rend très clair que vous ne pouvez pas modifier une liste itérée sauf avec l'itérateur lui-même, et vous donne des moyens efficaces de le faire sans copier la liste. Allez, Python!


30
2017-12-12 10:18



Il peut être judicieux de simplement créer une nouvelle liste si l'élément de liste actuel répond aux critères souhaités.

alors:

for item in originalList:
   if (item != badValue):
        newList.append(item)

et pour éviter d'avoir à re-coder l'ensemble du projet avec le nom des nouvelles listes:

originalList[:] = newList

notez, à partir de la documentation Python:

copy.copy (x)   Renvoie une copie superficielle de x.

copy.deepcopy (x)   Renvoie une copie complète de x.


9
2018-03-19 01:41



J'avais besoin de faire cela avec une énorme liste, et la duplication de la liste me semblait coûteuse, d'autant plus que dans mon cas le nombre de suppressions serait faible par rapport aux items qui restent. J'ai pris cette approche de bas niveau.

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

Ce que je ne sais pas, c'est l'efficacité d'un couple de suppressions par rapport à la copie d'une grande liste. S'il vous plaît commenter si vous avez un aperçu.


8
2018-03-13 20:54