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.
Réponses:
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:
Ou, en l'affectant à la tranche
somelist[:]
, vous pouvez muter la liste existante pour ne contenir que les éléments souhaités:Cette approche pourrait être utile s'il existe d'autres références à
somelist
ce besoin pour refléter les changements.Au lieu d'une compréhension, vous pouvez également utiliser
itertools
. En Python 2:Ou en Python 3:
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.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
False
comme le fait Python. Vous pouvez remplacerwhile somelist:
quelque chose de plus explicite commewhile len(somelist) > 0:
.la source
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.list_ass_slice()
fonction qui implémente lessomelist[:]=
appels enPySequence_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 efficacesomelist
modifiée dans les deux méthodes?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:
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.
la source
a
par le contenu de dictb
, utiliseza.clear(); a.update(b)
.x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];
Ceci réaffectex
le résultat de la compréhension de la liste, maisy
se réfère toujours à la liste d' origine['foo','bar','baz']
. Si vous vous y attendiezx
ety
pour 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 foisx
ety
ici) se réfèrent à la nouvelle liste.filter
fonction crée également une nouvelle liste, ne modifie pas les éléments en place ... seulementolist[:] = [i for i in olist if not dislike(i)]
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):
Un exemple:
la source
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'somelist
objet, donc j'utilise[:]
remove()
doit parcourir la liste entière pour chaque itération, donc cela prendra une éternité.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
range
parxrange
pour éviter de créer une liste codée en durla source
reversed()
intégréenumerate
renvoie un itérateur etreversed
attend une séquence. Je suppose que vous pourriez le fairereversed(list(enumerate(somelist)))
si cela ne vous dérange pas de créer une liste supplémentaire en mémoire.m
plus lent.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:
[:]
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:
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:
démarrer un nouveau tableau à partir de zéro, et
.append()
revenir à la fin: https://stackoverflow.com/a/1207460/895245Cette fois efficace, mais moins efficace en termes d'espace car il conserve une copie du tableau pendant l'itération.
utiliser
del
avec un index: https://stackoverflow.com/a/1207485/895245Ceci est plus efficace en termes d'espace car il distribue la copie du tableau, mais il est moins efficace en temps car les listes CPython sont implémentées avec des tableaux dynamiques .
Cela signifie que la suppression d'élément nécessite de reculer tous les éléments suivants d'un, qui est O (N).
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:
std::vector::erase
qui 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
ArrayList
et lesLinkedList
implémentations deListIterator
.Il ne semble pas non plus y avoir de type de liste liée explicite dans stdlib Python : Liste liée Python
la source
Votre meilleure approche pour un tel exemple serait une compréhension de la liste
Dans les cas où vous faites quelque chose de plus complexe que d'appeler une
determine
fonction, je préfère construire une nouvelle liste et simplement y ajouter au fur et à mesure. Par exempleLa copie de la liste à l'aide de
remove
peut 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 uneO(n)
remove
opération pour chaque élément supprimé, ce qui en fait unO(n^2)
algorithme.la source
Pour ceux qui aiment la programmation fonctionnelle:
ou
la source
filter
Pythonic. 2. Si vous avez besoin d'unlambda
pour utilisermap
oufilter
, la liste comp ou genexpr est toujours la meilleure option;map
etfilter
peut ê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 besoinlambda
que listcomp / genexpr pourrait éviter.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.
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.
la source
list
une 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-êtreOrderedDict
?newlist = []
, puisnewlist.append(array[i])
juste avantdel array[i]
?list()
s'agit d'une liste chaînée, l'accès aléatoire est cher, s'illist()
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.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:
et pour éviter d'avoir à recoder tout le projet avec le nouveau nom des listes:
note, à partir de la documentation Python:
la source
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:
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):
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:
Ce code s'exécute sans aucune erreur, mais regardons la liste qu'il génère:
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:
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:
Cependant, la sortie sera identique à celle d'avant:
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" (==).
Nous pouvons faire une vraie copie en utilisant copy.copy (). Ensuite, tout fonctionne comme prévu:
Enfin, il existe une solution plus propre que de faire une copie entièrement nouvelle de L1. La fonction reverse ():
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.
la source
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.
enumerate
vous donne accès à l'élément et à l'index à la fois.reversed
afin que les indices que vous allez supprimer ultérieurement ne changent pas pour vous.la source
Vous voudrez peut-être utiliser
filter()
disponible comme intégré.Pour plus de détails , cliquez ici
la source
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 ,
La valeur de
i
n'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é.la source
Vous pouvez essayer de faire une boucle en sens inverse, donc pour some_list, vous ferez quelque chose comme:
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).
la source
reversed(list(enumerate(some_list)))
serait plus simple que le calcul d'index vous-même.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:
la source
bad
choses, en faire quelque chose et aussi faire desgood
choses en une seule boucle?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.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é:
`` ''
`` ''
la source
TLDR:
J'ai écrit une bibliothèque qui vous permet de faire ceci:
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.py
partir icihttps://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.Cela produira la sortie suivante:
Ci-dessus, nous avons utilisé la
pop
méthode sur l'objet liste fluide. Sont également mis en œuvre d' autres méthodes itératives communes telles quedel fluidL[i]
,.remove
,.insert
,.append
,.extend
. La liste peut également être modifiée à l'aide de tranches (sort
etreverse
méthodes ne sont pas implémentées).La seule condition est que vous devez uniquement modifier la liste en place, si à tout moment
fluidL
oul
été réaffecté à un autre objet de liste, le code ne fonctionnerait pas. L'fluidL
objet d' origine serait toujours utilisé par la boucle for mais deviendrait hors de portée pour nous de le modifier.c'est à dire
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.
Cela produira les éléments suivants:
La
FluidIterable
classe 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:Plus d'exemples / tests peuvent être trouvés dans la
if __name__ is "__main__":
section au bas defluidIter.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
Mais que se passe-t-il si le résultat de la
testFunc
dépend des éléments qui ontnewList
dé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.
La sortie et la liste réduite finale sont affichées ci-dessous
la source
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.some_list[:] = [x for x in some_list if not some_condition(y)]
oùy
est un élément de liste différentx
. Il ne serait pas non plus possible d'écriresome_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)]
.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
iterator
traversfilter
.Il y a un exemple (obtenez les cotes dans le tuple):
Attention: vous ne pouvez pas non plus gérer les itérateurs. Les itérateurs sont parfois meilleurs que les séquences.
la source
pour la boucle sera itérée à travers l'index ..
considérez que vous avez une liste,
vous utilisez une variable de liste appelée
lis
. et vous en utilisant même pour supprimer ..votre variable
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.
puis la valeur suivante (65) passe à l'index précédent.
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.
faites de même la copie de la liste en utilisant
list[::]
maintenant tu vas donner,
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
la source
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:
la source
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
return
ou unbreak
. Par exemple: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.
la source
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 estsum 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
del
pour 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.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:
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.
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
la source
Pour tout ce qui a le potentiel d'être vraiment gros, j'utilise ce qui suit.
Cela devrait être beaucoup plus rapide qu'autre chose.
la source
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.
la source
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
De cette façon, vous n'avez pas besoin de copier la liste et c'est plus facile à comprendre.
la source
posez une liste de nombres et vous souhaitez supprimer tous les non divisibles par 3,
en utilisant
list comprehension
, cela va créer une nouvelle liste et créer un nouvel espace mémoireen utilisant la
lambda filter
fonction, cela créera une nouvelle liste résultante et consommera de l'espace mémoiresans consommer d'espace mémoire pour la nouvelle liste et modifier la liste existante
la source