web-dev-qa-db-fra.com

Python: ajout d'un élément à la liste lors de l'itération

Je sais qu'il n'est pas autorisé de supprimer des éléments lors de l'itération d'une liste, mais est-il autorisé d'ajouter des éléments à une liste python pendant l'itération. Voici un exemple:

    for a in myarr:
      if somecond(a):
          myarr.append(newObj())

J'ai essayé cela dans mon code et cela semble bien fonctionner, mais je ne sais pas si c'est parce que j'ai juste de la chance et que ça va casser à un moment donné dans le futur?

EDIT: Je préfère ne pas copier la liste car "myarr" est énorme, et donc ce serait trop lent. J'ai également besoin de vérifier les objets ajoutés avec "somecond ()".

EDIT: À un moment donné, "somecond (a)" sera faux, il ne peut donc pas y avoir de boucle infinie.

EDIT: Quelqu'un a posé des questions sur la fonction "somecond ()". Chaque objet dans myarr a une taille, et chaque fois que "une seconde (a)" est vrai et qu'un nouvel objet est ajouté à la liste, le nouvel objet aura une taille inférieure à a. "somecond ()" a un epsilon pour savoir comment les petits objets peuvent être et s'ils sont trop petits, il retournera "false"

45
WesDec

Vous pouvez utiliser le islice d'itertools pour créer un itérateur sur une plus petite partie de la liste. Ensuite, vous pouvez ajouter des entrées à la liste sans affecter les éléments sur lesquels vous répétez:

islice( myarr, 0, len(myarr)-1 )

Encore mieux, vous n'avez même pas à répéter tous les éléments. Vous pouvez incrémenter une taille de pas.

6
wheaties

Pourquoi ne le faites-vous pas simplement de la manière idiomatique C? Cela devrait être à l'épreuve des balles, mais ce ne sera pas rapide. Je suis assez sûr d'indexer dans une liste dans Python parcourt la liste liée, donc c'est un algorithme "Shlemiel the Painter". Mais je n'ai pas tendance à m'inquiéter de l'optimisation jusqu'à ce qu'il devienne clair qu'un une section particulière du code est vraiment un problème. D'abord, faites-le fonctionner, puis souciez-vous de le rendre rapide, si nécessaire.

Si vous souhaitez parcourir tous les éléments:

i = 0  
while i < len(some_list):  
  more_elements = do_something_with(some_list[i])  
  some_list.extend(more_elements)  
  i += 1  

Si vous voulez seulement parcourir les éléments qui étaient à l'origine dans la liste:

i = 0  
original_len = len(some_list)  
while i < original_len:  
  more_elements = do_something_with(some_list[i])  
  some_list.extend(more_elements)  
  i += 1
46
Johnny

bien, selon http://docs.python.org/tutorial/controlflow.html

Il n'est pas sûr de modifier la séquence en cours d'itération dans la boucle (cela ne peut se produire que pour les types de séquence mutables, tels que les listes). Si vous devez modifier la liste que vous parcourez (par exemple, pour dupliquer les éléments sélectionnés), vous devez itérer sur une copie.

19
Rohan Monga

Tu peux le faire.

bonus_rows = []
for a in myarr:
  if somecond(a):
      bonus_rows.append(newObj())
myarr.extend( bonus_rows )
6
S.Lott

En bref : Si vous êtes absolument sûr que tous les nouveaux objets échouent somecond() check, alors votre code fonctionne bien, il gaspille juste certains temps itération des objets nouvellement ajoutés.

Avant de donner une bonne réponse, vous devez comprendre pourquoi il considère une mauvaise idée de modifier la liste/dict pendant l'itération. Lors de l'utilisation de l'instruction for, Python essaie d'être intelligent , et renvoie à chaque fois un élément calculé dynamiquement. Prenons list comme exemple, python se souvient d'un index et chaque fois qu'il renvoie l[index] à toi. Si vous modifiez l, le résultat l[index] peut être désordonné.

[~ # ~] note [~ # ~] : Voici une stackoverflow question pour le démontrer.

Le pire des cas pour ajouter un élément pendant l'itération est boucle infinie , essayez (ou non si vous pouvez lire un bogue) ce qui suit dans un python REPL:

import random

l = [0]
for item in l:
    l.append(random.randint(1, 1000))
    print item

Il imprime des nombres sans arrêt jusqu'à ce que la mémoire soit épuisée ou détruite par le système/l'utilisateur.

Comprenez la raison interne, discutons des solutions. Voici quelques-uns:

1. faire une copie de la liste d'origine

Itérer la liste d'origine et modifier celle copiée.

result = l[:]
for item in l:
    if somecond(item):
        result.append(Obj())

2. contrôler la fin de la boucle

Au lieu de gérer le contrôle en python, vous décidez comment itérer la liste:

length = len(l)
for index in range(length):
    if somecond(l[index]):
        l.append(Obj())

Avant d'itérer, calculez la longueur de la liste et ne bouclez que length fois.

3. stocker les objets ajoutés dans une nouvelle liste

Au lieu de modifier la liste d'origine, stockez le nouvel objet dans une nouvelle liste et concaténez-les ensuite.

added = [Obj() for item in l if somecond(item)]
l.extend(added)
5
cizixs

Accédez à vos éléments de liste directement par i. Ensuite, vous pouvez ajouter à votre liste:

for i in xrange(len(myarr)):
    if somecond(a[i]):
        myarr.append(newObj())
3
eumiro

faire une copie de votre liste d'origine, itérer dessus, voir le code modifié ci-dessous

for a in myarr[:]:
      if somecond(a):
          myarr.append(newObj())
3
shahjapan

J'ai eu un problème similaire aujourd'hui. J'avais une liste d'articles qui devaient être vérifiés; si les objets ont réussi la vérification, ils ont été ajoutés à une liste de résultats. S'ils ne l'ont pas réussissent, je les ai un peu modifiés et s'ils peuvent toujours fonctionner (taille> 0 après le changement), je les ajouterais au dos de la liste pour revérifier.

Je suis allé pour une solution comme

items = [...what I want to check...]
result = []
while items:
    recheck_items = []
    for item in items:
        if check(item):
            result.append(item)
        else:
            item = change(item)  # Note that this always lowers the integer size(),
                                 # so no danger of an infinite loop
            if item.size() > 0:
                recheck_items.append(item)
    items = recheck_items  # Let the loop restart with these, if any

Ma liste est effectivement une file d'attente, aurait probablement dû utiliser une sorte de file d'attente. Mais mes listes sont petites (comme 10 articles) et cela fonctionne aussi.

2
RemcoGerlich

Vous pouvez utiliser un index et une boucle while au lieu d'une boucle for si vous souhaitez que la boucle boucle également sur les éléments ajoutés à la liste pendant la boucle:

i = 0
while i < len(myarr):
    a = myarr[i];
    i = i + 1;
    if somecond(a):
        myarr.append(newObj())
2
HelloGoodbye

Solution alternative sur une seule ligne:

reduce(lambda x,y : x +[newObj] if somecond else x,myarr,myarr)
1
Akshay Hazari

Extension de la réponse de S.Lott pour que les nouveaux éléments soient également traités:

todo = myarr
done = []
while todo:
    added = []
    for a in todo:
        if somecond(a):
            added.append(newObj())
    done.extend(todo)
    todo = added

La liste finale est dans done.

1
Mike DeSimone