Comment supprimer des éléments d'une liste pendant l'itération?

934

J'itère 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 dois-je utiliser à la place de code_to_remove_tup? Je ne peux pas comprendre comment supprimer l'élément de cette façon.

lfaraone
la source
La plupart des réponses sur cette page n'expliquent pas vraiment pourquoi la suppression d'éléments lors d'une itération sur une liste produit des résultats étranges, mais la réponse acceptée dans cette question le fait et est probablement une meilleure dupe pour les débutants qui rencontrent ce problème pour la première fois.
ggorlen

Réponses:

828

Vous pouvez utiliser une compréhension de liste 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 l'affectant à la tranche somelist[:], vous pouvez muter la liste existante pour ne contenir 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 à somelistce besoin pour 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)

Par souci de clarté et pour ceux qui trouvent l'utilisation de la [:]notation hackish ou floue, voici une alternative plus explicite. Théoriquement, il devrait fonctionner de la même manière en termes d'espace et de temps que les monolignes ci-dessus.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

Il fonctionne également dans d'autres langages qui peuvent ne pas avoir la capacité de remplacement des éléments des listes Python, avec un minimum de modifications. Par exemple, toutes les langues ne transforment pas des listes vides en un Falsecomme le fait Python. Vous pouvez remplacer while somelist:quelque chose de plus explicite comme while len(somelist) > 0:.

David Raznick
la source
4
Pouvez-vous accélérer le processus si vous savez que seuls quelques-uns seront supprimés, c'est-à-dire, supprimez-les uniquement et laissez les autres en place plutôt que de les réécrire?
highBandWidth
20
Et si ma liste est énorme et ne peut pas se permettre d'en faire une copie?
jpcgt
15
@jpcgt Vous devez utiliser somelist[:] = (x for x in somelist if determine(x))ceci pour créer un générateur qui peut ne pas créer de copies inutiles.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()fonction qui implémente les somelist[:]=appels en PySequence_Fast()interne. Cette fonction renvoie toujours une liste, c'est-à-dire que la solution de @Alex Martelli qui utilise déjà une liste au lieu d'un générateur est probablement plus efficace
jfs
6
Souhaitez-vous expliquer quelles sont les différences entre l'attribution de la compréhension de la liste à la liste et le clone de la liste, s'il vous plaît? La liste d'origine ne serait-elle pas somelistmodifiée dans les deux méthodes?
Bowen Liu
589

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 ensuite le même nom que l'ancienne liste, 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 votre liste est accessible via plusieurs références, le fait que vous ne faites que réinstaller une des références et ne modifiez PAS l'objet de 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 des listes ET la sémantique requise de la modification 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'EST PAS affectée à un nom de barre - elle est affectée à une tranche de liste qui se trouve être la liste entière, remplaçant ainsi le contenu de la liste dans le même objet de liste Python , plutôt que de simplement réinstaller une référence (de l'objet de liste précédent au nouvel objet de liste) comme les autres réponses.

Alex Martelli
la source
1
Comment puis-je faire la même affectation en tranches avec un dict? En Python 2.6?
PaulMcG
11
@ Paul: Étant donné que les pronostics ne sont pas ordonnés, les tranches n'ont aucun sens pour les pronostics. Si vous souhaitez remplacer le contenu de dict apar le contenu de dict b, utilisez a.clear(); a.update(b).
Sven Marnach
1
Pourquoi «réinstaller» l'une des références en remplaçant ce à quoi la variable fait référence pour provoquer des bugs? Il semble que cela ne soit un problème potentiel que dans les applications multithread, et non monothread.
Derek Dahmer
59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Ceci réaffecte xle résultat de la compréhension de la liste, mais yse réfère toujours à la liste d' origine['foo','bar','baz'] . Si vous vous y attendiez xet ypour vous référer à la même liste, vous avez peut-être introduit des bugs. Vous empêchez en assignant à une tranche de la liste entière, comme le montre Alex, et je montre ici: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. La liste est modifiée sur place. en s'assurant que toutes les références à la liste (à la fois xet yici) se réfèrent à la nouvelle liste.
Steven T. Snyder
en fait, utiliser la filterfonction crée également une nouvelle liste, ne modifie pas les éléments en place ... seulementolist[:] = [i for i in olist if not dislike(i)]
John Strood
303

Vous devez d'abord prendre une copie de la liste et l'itérer, sinon l'itération échouera avec des résultats qui peuvent être 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
[]
Lennart Regebro
la source
13
@Zen Parce que le second itère sur une copie de la liste. Ainsi, lorsque vous modifiez la liste d'origine, vous ne modifiez pas la copie sur laquelle vous parcourez.
Lennart Regebro
3
Quoi de mieux pour faire somelist [:] que pour list (somelist)?
Mariusz Jamro
3
list(somelist)convertira un itérable en liste. somelist[:]crée une copie d'un objet qui prend en charge le découpage. Ils ne font donc pas nécessairement la même chose. Dans ce cas, je veux faire une copie de l' somelistobjet, donc j'utilise[:]
Lennart Regebro
33
Remarque à tous ceux qui lisent ceci, c'est très lent pour les listes. remove()doit parcourir la liste entière pour chaque itération, donc cela prendra une éternité.
vitiral
7
Le temps Big O n'a pas d'importance lorsqu'il s'agit de listes de seulement une douzaine d'articles. Souvent clair et simple à comprendre pour les futurs programmeurs, c'est bien plus précieux que les performances.
Steve
127
for i in range(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 :-)

Utilisateurs de Python 2: remplacez rangepar xrangepour éviter de créer une liste codée en dur

John Machin
la source
13
Dans les versions récentes de Python, vous pouvez le faire encore plus proprement en utilisant le programme reversed()intégré
ncoghlan
16
reverse () ne crée pas de nouvelle liste, il crée un itérateur inversé sur la séquence fournie. Comme enumerate (), vous devez l'encapsuler dans list () pour en extraire une liste. Vous pensez peut - être triés (), qui fait créer une nouvelle liste à chaque fois (il doit, il peut donc le tri).
ncoghlan
1
@Mauris car enumeraterenvoie un itérateur et reversedattend une séquence. Je suppose que vous pourriez le faire reversed(list(enumerate(somelist)))si cela ne vous dérange pas de créer une liste supplémentaire en mémoire.
drevicko
2
C'est O (N * M) pour les tableaux, c'est très lent si vous supprimez de nombreux éléments d'une grande liste. Donc pas recommandé.
Sam Watkins du
2
@SamWatkins Oui, cette réponse est pour quand vous supprimez quelques éléments d'un très grand tableau. Moins d'utilisation de la mémoire, mais cela peut être mplus lent.
Navin
52

Tutoriel officiel Python 2 4.2. "pour les déclarations"

https://docs.python.org/2/tutorial/controlflow.html#for-statements

Cette partie de la documentation indique clairement que:

  • vous devez faire une copie de la liste itérée pour la modifier
  • une façon de le faire est avec la notation de tranche [:]

Si vous devez modifier la séquence sur laquelle vous faites une itération à l'intérieur de la boucle (par exemple pour dupliquer les éléments sélectionnés), il est recommandé de faire d'abord une copie. L'itération sur une séquence n'implique pas implicitement une copie. La notation par tranche rend cela particulièrement pratique:

>>> words = ['cat', 'window', 'defenestrate']
>>> 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']

Documentation Python 2 7.3. "La déclaration for"

https://docs.python.org/2/reference/compound_stmts.html#for

Cette partie de la documentation dit encore une fois que vous devez faire une copie et donne un exemple de suppression:

Remarque: Il y a une subtilité lorsque la séquence est modifiée par la boucle (cela ne peut se produire que pour les séquences mutables, c'est-à-dire les listes). Un compteur interne est utilisé pour garder la trace de l'élément qui sera utilisé ensuite, et cela 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 actuel (ou précédent) de la séquence, l'élément suivant sera ignoré (car il obtient l'index de l'élément actuel 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 actuel, l'élément actuel sera à nouveau traité la prochaine fois dans la boucle. Cela peut conduire à des bugs 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)

Cependant, je ne suis pas d'accord avec cette implémentation, car .remove()il faut parcourir la liste entière pour trouver la valeur.

Meilleures solutions de contournement

Soit:

En général, vous voulez simplement opter pour l' .append()option plus rapide par défaut, sauf si la mémoire est un gros problème.

Python pourrait-il mieux faire cela?

Il semble que cette API Python particulière pourrait être améliorée. Comparez-le, par exemple, avec:

  • Java ListIterator :: supprimer les documents "Cet appel ne peut être effectué qu'une seule fois par appel vers le suivant ou le précédent"
  • C ++ std::vector::erasequi renvoie un élément d'interaction valide à l'élément après celui supprimé

Les deux expliquent clairement que vous ne pouvez pas modifier une liste en cours d'itération, sauf avec l'itérateur lui-même, et vous offre des moyens efficaces de le faire sans copier la liste.

La raison sous-jacente est peut-être que les listes Python sont supposées être soutenues par un tableau dynamique, et donc tout type de suppression sera de toute façon inefficace en temps, tandis que Java a une hiérarchie d'interface plus agréable avec les deux ArrayListet les LinkedListimplémentations de ListIterator.

Il ne semble pas non plus y avoir de type de liste liée explicite dans stdlib Python : Liste liée Python

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
48

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

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

Dans les cas où vous faites quelque chose de plus complexe que d'appeler une determinefonction, je préfère construire une nouvelle liste et simplement y ajouter au fur et à mesure. 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

La copie de la liste à l'aide de removepeut 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 la liste entière, et également d'effectuer une O(n) removeopération pour chaque élément supprimé, 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)
Eli Courtwright
la source
37

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))
Cide
la source
1. La compréhension des listes et les expressions génératrices sont empruntées à Haskell, un langage fonctionnel pur; ils sont exactement aussi fonctionnels que filterPythonic. 2. Si vous avez besoin d'un lambdapour utiliser mapou filter, la liste comp ou genexpr est toujours la meilleure option; mapet filterpeut être légèrement plus rapide lorsque la fonction transform / predicate est un Python intégré implémenté en C et que l'itérable n'est pas trivialement petit, mais ils sont toujours plus lents lorsque vous en avez besoin lambdaque listcomp / genexpr pourrait éviter.
ShadowRanger
13

Je devais le faire avec une énorme liste, et la duplication de la liste semblait coûteuse, d'autant plus que dans mon cas, le nombre de suppressions serait faible par rapport aux éléments qui restent. J'ai adopté 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é de quelques suppressions par rapport à la copie d'une grande liste. Veuillez commenter si vous avez un aperçu.

Michael
la source
Dans mon cas, je dois déplacer ces éléments «indésirables» dans une autre liste. Avez-vous un nouveau commentaire sur cette solution? Je pense également qu'il vaut mieux utiliser certaines suppressions au lieu de dupliquer la liste.
gustavovelascoh
C'est la bonne réponse si la performance est un problème (bien que identique à @Alexey). Cela dit, le choix d' listune structure de données en premier lieu doit être soigneusement envisagé, car la suppression du milieu d'une liste prend un temps linéaire dans la longueur de la liste. Si vous n'avez pas vraiment besoin d'un accès aléatoire au k-ème élément séquentiel, envisagez peut-être OrderedDict?
max
@GVelascoh pourquoi ne pas créer newlist = [], puis newlist.append(array[i])juste avant del array[i]?
max
2
Notez que cela est probablement inefficace en temps: s'il list()s'agit d'une liste chaînée, l'accès aléatoire est cher, s'il list()s'agit d'un tableau, les suppressions sont coûteuses car elles nécessitent de déplacer tous les éléments suivants vers l'avant. Un itérateur décent pourrait améliorer la mise en œuvre de la liste chaînée. Cela pourrait toutefois être peu encombrant.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
10

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

donc:

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

et pour éviter d'avoir à recoder tout le projet avec le nouveau nom des listes:

originalList[:] = newList

note, à partir de la documentation Python:

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

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

ntk4
la source
3
Cela n'ajoute aucune nouvelle information qui n'était pas dans la réponse acceptée des années plus tôt.
Mark Amery
2
C'est simple et juste une autre façon de voir un problème @MarkAmery. Il est moins condensé pour les personnes qui n'aiment pas la syntaxe de codage compressée.
ntk4
9

Cette réponse a été écrite à l'origine en réponse à une question qui a depuis été marquée comme doublon: Suppression des coordonnées de la liste sur python

Il y a deux problèmes dans votre code:

1) Lorsque vous utilisez remove (), vous essayez de supprimer des entiers alors que vous devez supprimer un tuple.

2) La boucle for sautera les éléments de votre liste.

Voyons ce qui se passe lorsque nous exécutons votre code:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

Le premier problème est que vous passez à la fois 'a' et 'b' à remove (), mais remove () n'accepte qu'un seul argument. Alors, comment pouvons-nous obtenir remove () pour fonctionner correctement avec votre liste? Nous devons déterminer quel est chaque élément de votre liste. Dans ce cas, chacun est un tuple. Pour voir cela, accédons à un élément de la liste (l'indexation commence à 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Ah! Chaque élément de L1 est en fait un tuple. C'est donc ce que nous devons passer pour supprimer (). Les tuples en python sont très faciles, ils sont simplement créés en mettant des valeurs entre parenthèses. "a, b" n'est pas un tuple, mais "(a, b)" est un tuple. Nous modifions donc votre code et le réexécutons:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Ce code s'exécute sans aucune erreur, mais regardons la liste qu'il génère:

L1 is now: [(1, 2), (5, 6), (1, -2)]

Pourquoi (1, -2) est-il toujours dans votre liste? Il s'avère que modifier la liste tout en utilisant une boucle pour itérer dessus est une très mauvaise idée sans soin particulier. La raison pour laquelle (1, -2) reste dans la liste est que les emplacements de chaque élément de la liste ont changé entre les itérations de la boucle for. Voyons ce qui se passe si nous alimentons le code ci-dessus une liste plus longue:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Comme vous pouvez le déduire de ce résultat, chaque fois que l'instruction conditionnelle prend la valeur true et qu'un élément de liste est supprimé, l'itération suivante de la boucle ignore l'évaluation de l'élément suivant dans la liste car ses valeurs sont désormais situées à différents indices.

La solution la plus intuitive consiste à copier la liste, puis à parcourir la liste d'origine et à modifier uniquement la copie. Vous pouvez essayer de le faire comme ceci:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Cependant, la sortie sera identique à celle d'avant:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

En effet, lorsque nous avons créé L2, python n'a pas réellement créé un nouvel objet. Au lieu de cela, il a simplement référencé L2 au même objet que L1. Nous pouvons vérifier cela avec 'is' qui est différent de simplement "égal" (==).

>>> L2=L1
>>> L1 is L2
True

Nous pouvons faire une vraie copie en utilisant copy.copy (). Ensuite, tout fonctionne comme prévu:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Enfin, il existe une solution plus propre que de faire une copie entièrement nouvelle de L1. La fonction reverse ():

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Malheureusement, je ne peux pas décrire correctement le fonctionnement de reverse (). Il renvoie un objet 'listreverseiterator' lorsqu'une liste lui est transmise. Pour des raisons pratiques, vous pouvez le considérer comme créant une copie inversée de son argument. C'est la solution que je recommande.

Cinghiale
la source
4

Si vous voulez faire autre chose pendant l'itération, il peut être intéressant d'obtenir à la fois l'index (qui vous garantit de pouvoir le référencer, par exemple si vous avez une liste de dict) et le contenu réel de l'élément de liste.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumeratevous donne accès à l'élément et à l'index à la fois. reversedafin que les indices que vous allez supprimer ultérieurement ne changent pas pour vous.

fantabolique
la source
Pourquoi l'obtention de l'index est-elle plus pertinente dans le cas où vous avez une liste de condamnations que dans le cas de tout autre type de liste? Pour autant que je sache, cela n'a aucun sens.
Mark Amery
4

Vous voudrez peut-être utiliser filter()disponible comme intégré.

Pour plus de détails , cliquez ici

Xolve
la source
4

La plupart des réponses ici vous demandent de créer une copie de la liste. J'ai eu un cas d'utilisation où la liste était assez longue (110 000 éléments) et il était plus intelligent de continuer à réduire la liste à la place.

Tout d'abord, vous devrez remplacer la boucle foreach par la boucle while ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

La valeur de in'est pas modifiée dans le bloc if, car vous souhaiterez obtenir la valeur du nouvel élément DANS LE MÊME INDICE, une fois l'ancien élément supprimé.

Mujeeb
la source
3

Vous pouvez essayer de faire une boucle en sens inverse, donc pour some_list, vous ferez quelque chose comme:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

De cette façon, l'index est aligné et ne souffre pas des mises à jour de la liste (que vous pop ou non l'élément cur).

Queequeg
la source
La boucle reversed(list(enumerate(some_list)))serait plus simple que le calcul d'index vous-même.
Mark Amery
@MarkAmery ne pense pas que vous pouvez modifier la liste de cette façon.
Queequeg
3

Une solution possible, utile si vous voulez non seulement supprimer certaines choses, mais aussi faire quelque chose avec tous les éléments dans une seule boucle:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1
Alexey
la source
Vous devriez vraiment utiliser des compréhensions. Ils sont beaucoup plus faciles à comprendre.
Beefster
Que faire si je veux supprimer des badchoses, en faire quelque chose et aussi faire des goodchoses en une seule boucle?
Alexey
1
En fait, je me suis rendu compte qu'il y avait de l'intelligence ici en ce que vous faites une copie de la liste avec une tranche ouverte ( alist[:]) Et comme vous pourriez faire quelque chose de fantaisiste, cela a en fait un cas d'utilisation. Une bonne révision est bonne. Prenez mon vote positif.
Beefster
2

Je devais faire quelque chose de similaire et dans mon cas, le problème était la mémoire - je devais fusionner plusieurs objets de jeu de données dans une liste, après avoir fait quelques trucs avec eux, en tant que nouvel objet, et je devais me débarrasser de chaque entrée dans laquelle je fusionnais. évitez de les dupliquer tous et de faire exploser la mémoire. Dans mon cas, avoir les objets dans un dictionnaire au lieu d'une liste a bien fonctionné:

`` ''

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

`` ''

rafa
la source
2

TLDR:

J'ai écrit une bibliothèque qui vous permet de faire ceci:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

Il est préférable d'utiliser une autre méthode si possible qui ne nécessite pas de modifier votre itérable tout en itérant dessus, mais pour certains algorithmes, cela peut ne pas être aussi simple. Et donc si vous êtes sûr de vouloir vraiment le modèle de code décrit dans la question d'origine, c'est possible.

Devrait fonctionner sur toutes les séquences mutables et pas seulement sur les listes.


Réponse complète:

Edit: Le dernier exemple de code dans cette réponse donne un cas d'utilisation pour savoir pourquoi vous pourriez parfois vouloir modifier une liste en place plutôt que d'utiliser une compréhension de liste. La première partie des réponses sert de tutoriel sur la façon dont un tableau peut être modifié sur place.

La solution découle de cette réponse (pour une question connexe) de senderle. Ce qui explique comment l'index du tableau est mis à jour lors de l'itération dans une liste qui a été modifiée. La solution ci-dessous est conçue pour suivre correctement l'index du tableau même si la liste est modifiée.

Télécharger à fluidIter.pypartir ici https://github.com/alanbacon/FluidIterator , il est juste un seul fichier , donc pas besoin d'installer git. Il n'y a pas de programme d'installation, vous devrez donc vous assurer que le fichier se trouve bien dans le chemin python. Le code a été écrit pour python 3 et n'est pas testé sur python 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Cela produira la sortie suivante:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Ci-dessus, nous avons utilisé la popméthode sur l'objet liste fluide. Sont également mis en œuvre d' autres méthodes itératives communes telles que del fluidL[i], .remove, .insert, .append,.extend . La liste peut également être modifiée à l'aide de tranches ( sortetreverse méthodes ne sont pas implémentées).

La seule condition est que vous devez uniquement modifier la liste en place, si à tout moment fluidLoul été réaffecté à un autre objet de liste, le code ne fonctionnerait pas. L' fluidLobjet d' origine serait toujours utilisé par la boucle for mais deviendrait hors de portée pour nous de le modifier.

c'est à dire

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Si nous voulons accéder à la valeur d'index actuelle de la liste, nous ne pouvons pas utiliser l'énumération, car cela ne compte que le nombre de fois où la boucle for s'est exécutée. Au lieu de cela, nous utiliserons directement l'itérateur.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Cela produira les éléments suivants:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

La FluidIterableclasse fournit simplement un wrapper pour l'objet de liste d'origine. L'objet d'origine est accessible en tant que propriété de l'objet fluide comme ceci:

originalList = fluidArr.fixedIterable

Plus d'exemples / tests peuvent être trouvés dans la if __name__ is "__main__":section au bas de fluidIter.py. Ceux-ci méritent d'être examinés car ils expliquent ce qui se passe dans diverses situations. Tels que: Remplacement d'une grande partie de la liste à l'aide d'une tranche. Ou en utilisant (et en modifiant) le même itérable dans les boucles imbriquées.

Comme je l'ai dit pour commencer: c'est une solution compliquée qui nuira à la lisibilité de votre code et le rendra plus difficile à déboguer. Par conséquent, d'autres solutions telles que les compréhensions de liste mentionnées dans la réponse de David Raznick doivent être envisagées en premier. Cela étant dit, j'ai trouvé des moments où cette classe m'a été utile et a été plus facile à utiliser que de garder une trace des indices des éléments à supprimer.


Edit: Comme mentionné dans les commentaires, cette réponse ne présente pas vraiment de problème pour lequel cette approche apporte une solution. Je vais essayer de répondre à cela ici:

La compréhension des listes fournit un moyen de générer une nouvelle liste, mais ces approches ont tendance à considérer chaque élément isolément plutôt que l'état actuel de la liste dans son ensemble.

c'est à dire

newList = [i for i in oldList if testFunc(i)]

Mais que se passe-t-il si le résultat de la testFuncdépend des éléments qui ont newListdéjà été ajoutés ? Ou les éléments encoreoldList qui s'y trouvent pourraient être ajoutés ensuite? Il pourrait toujours y avoir un moyen d'utiliser une compréhension de liste, mais cela commencera à perdre son élégance, et pour moi, il semble plus facile de modifier une liste en place.

Le code ci-dessous est un exemple d'algorithme qui souffre du problème ci-dessus. L'algorithme réduira une liste afin qu'aucun élément ne soit un multiple d'un autre élément.

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

La sortie et la liste réduite finale sont affichées ci-dessous

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]
Résonance
la source
Il est difficile de dire si cela est sur-conçu car il n'est pas clair quel problème il essaie de résoudre; Qu'est-ce que la suppression d'éléments à l'aide de cette approche some_list[:] = [x for x in some_list if not some_condition(x)]permet d'obtenir sans succès? Sans réponse à cela, pourquoi devrait-on croire que le téléchargement et l'utilisation de votre bibliothèque de 600 lignes complète avec des fautes de frappe et du code commenté est une meilleure solution à leur problème que le one-liner? -1.
Mark Amery
@MarkAmery. Le cas d'utilisation principal pour quand il s'agit de déterminer si un élément doit être supprimé (ou ajouté ou déplacé) en fonction non seulement de l'élément lui-même, mais de l'état d'un autre élément dans la liste ou de l'état de la liste en tant que entier. Par exemple, il n'est pas possible avec des compréhensions de liste d'écrire quelque chose comme d' some_list[:] = [x for x in some_list if not some_condition(y)]yest un élément de liste différent x. Il ne serait pas non plus possible d'écrire some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Resonance
2

La méthode la plus efficace est la compréhension des listes, beaucoup de gens montrent leur cas, bien sûr, c'est aussi un bon moyen de passer au iteratortravers filter.

Filterreçoit une fonction et une séquence. Filterapplique successivement la fonction transmise à chaque élément, puis décide de conserver ou de supprimer l'élément selon que la valeur de retour de la fonction est Trueou False.

Il y a un exemple (obtenez les cotes dans le tuple):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Attention: vous ne pouvez pas non plus gérer les itérateurs. Les itérateurs sont parfois meilleurs que les séquences.

chseng
la source
2

pour la boucle sera itérée à travers l'index ..

considérez que vous avez une liste,

[5, 7, 13, 29, 65, 91]

vous utilisez une variable de liste appelée lis. et vous en utilisant même pour supprimer ..

votre variable

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

lors de la 5ème itération,

votre numéro 35 n'était pas un nombre premier, vous l'avez donc supprimé d'une liste.

lis.remove(y)

puis la valeur suivante (65) passe à l'index précédent.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

donc le pointeur de la 4ème itération effectuée est passé au 5ème

c'est pourquoi votre boucle ne couvre pas 65 depuis son déplacement dans l'index précédent.

vous ne devez donc pas référencer la liste dans une autre variable qui fait toujours référence à l'original au lieu de la copier.

ite = lis #dont do it will reference instead copy

faites de même la copie de la liste en utilisant list[::]

maintenant tu vas donner,

[5, 7, 13, 29]

Le problème est que vous avez supprimé une valeur d'une liste pendant l'itération, puis votre index de liste s'effondrera.

afin que vous puissiez essayer la compréhension à la place.

qui supporte tous les itérables comme, list, tuple, dict, string etc

Mohideen bin Mohammed
la source
Cela m'a aidé à comprendre pourquoi mon code échouait.
Wahid Sadik
2

Si vous souhaitez supprimer des éléments d'une liste pendant l'itération, utilisez une boucle while pour pouvoir modifier l'index en cours et l'index de fin après chaque suppression.

Exemple:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1
Sans nom
la source
1

Les autres réponses sont correctes, c'est généralement une mauvaise idée de supprimer d'une liste que vous êtes en train d'itérer. L'itération inversée évite les écueils, mais il est beaucoup plus difficile de suivre le code qui fait cela, donc généralement il vaut mieux utiliser une liste de compréhension ou filter.

Il y a cependant un cas où il est sûr de supprimer des éléments d'une séquence que vous êtes en train d'itérer: si vous ne supprimez qu'un seul élément pendant que vous itérez. Cela peut être assuré en utilisant un returnou un break. Par exemple:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

C'est souvent plus facile à comprendre qu'une compréhension de liste lorsque vous effectuez des opérations avec des effets secondaires sur le premier élément d'une liste qui remplit une condition, puis en supprimant cet élément de la liste immédiatement après.

Beefster
la source
1

Je peux penser à trois approches pour résoudre votre problème. À titre d'exemple, je vais créer une liste aléatoire de tuples somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. La condition que je choisis est sum of elements of a tuple = 15. Dans la liste finale, nous n'aurons que les tuples dont la somme n'est pas égale à 15.

Ce que j'ai choisi est un exemple choisi au hasard. N'hésitez pas à modifier la liste des tuples et la condition que j'ai choisie.

Méthode 1.> Utilisez le framework que vous avez suggéré (où l'on remplit un code à l'intérieur d'une boucle for). J'utilise un petit code avec delpour supprimer un tuple qui remplit ladite condition. Cependant, cette méthode manquera un tuple (qui satisfait à ladite condition) si deux tuples placés consécutivement remplissent la condition donnée.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Méthode 2.> Construisez une nouvelle liste qui contient des éléments (tuples) où la condition donnée n'est pas remplie (c'est la même chose que de supprimer des éléments de liste où la condition donnée est remplie). Voici le code pour cela:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Méthode 3.> Recherchez les indices où la condition donnée est remplie, puis utilisez les éléments de suppression (tuples) correspondant à ces indices. Voici le code pour cela.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

La méthode 1 et la méthode 2 sont plus rapides que la méthode 3 . La méthode 2 et la méthode 3 sont plus efficaces que la méthode 1. Je préfère method2 . Pour l'exemple susmentionné,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
la source
0

Pour tout ce qui a le potentiel d'être vraiment gros, j'utilise ce qui suit.

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Cela devrait être beaucoup plus rapide qu'autre chose.

CENTURION
la source
D'après ce que j'ai mesuré, NumPy commence à être plus rapide pour les listes de plus de 20 éléments et atteint un filtrage> 12x plus rapide pour les grandes listes de 1000 éléments et plus.
Georgy
0

Dans certaines situations, lorsque vous faites plus que simplement filtrer une liste un élément à la fois, vous souhaitez que votre itération change pendant l'itération.

Voici un exemple où la copie préalable de la liste est incorrecte, l'itération inverse est impossible et une compréhension de la liste n'est pas non plus une option.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
CodeKid
la source
0

Si vous utiliserez la nouvelle liste plus tard, vous pouvez simplement définir l'elem sur Aucun, puis le juger dans la dernière boucle, comme ceci

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

De cette façon, vous n'avez pas besoin de copier la liste et c'est plus facile à comprendre.

john zhang
la source
-1

posez une liste de nombres et vous souhaitez supprimer tous les non divisibles par 3,

list_number =[i for i in range(100)]

en utilisant list comprehension, cela va créer une nouvelle liste et créer un nouvel espace mémoire

new_list =[i for i in list_number if i%3!=0]

en utilisant la lambda filterfonction, cela créera une nouvelle liste résultante et consommera de l'espace mémoire

new_list = list(filter(lambda x:x%3!=0, list_number))

sans consommer d'espace mémoire pour la nouvelle liste et modifier la liste existante

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
la source