views:

191

answers:

7

I know that it is not allowed to remove elements while iterating a list, but is it allowed to add elements to a python list while iterating. Here is an example:

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

I have tried this in my code and it seems to works fine, however i dont know if its because i am just lucky and that it will break at some point in the future?

EDIT: i prefer not to copy the list since "myarr" is huge, and therefore it would be too slow. Also i need to check the appended objects with "somecond()".

EDIT: At some point "somecond(a)" will be false, so there can not be an infinite loop.

EDIT: Someone asked about the "somecond()" function. Each object in myarr has a size, and each time "somecond(a)" is true and a new object is appended to the list, the new object will have a size smaller than a. "somecond()" has an epsilon for how small objects can be and if they are too small it will return "false"

+2  A: 

make copy of your original list, iterate over it, see the modified code below

for a in myarr[:]:
      if somecond(a):
          myarr.append(newObj())
Tumbleweed
The problem is that the list is huge so it would be very slow to copy it everytime.
WesDec
@WesDec: Please **measure** before declaring it "very slow". It's a shallow copy. It's pretty quick.
S.Lott
@S.Lott: The list can easily be over 100 million elements, and the above loop is repeated many many times. Even a shallow copy would be slow.
WesDec
+8  A: 

well, according to http://docs.python.org/tutorial/controlflow.html

It is not safe to modify the sequence being iterated over in the loop (this can only happen for mutable sequence types, such as lists). If you need to modify the list you are iterating over (for example, to duplicate selected items) you must iterate over a copy.

bronzebeard
@bronzebeard: I am aware of that text, but often iterators in other languages support appending new elements to the end of the list, while iterating. I was hoping that python also supports that since it would make things more simple and more readable.
WesDec
i agree but the only solution is to make a copy, which is also explained in the tutorial.
bronzebeard
+1  A: 

The danger with doing this is that the elements added at the end will also be iterated through. This means that if those objects also satisfy somecond(), then more elements will be added on. This can create an infinite loop. For instance, here is a very simple infinite loop:

x = [1]
for a in x:
    x.append(a+1)

If you want to avoid copying the array, then you could use an index to loop through the items:

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

This isn't Pythonic, but it will avoid copying the array.

Justin Peel
This is ok and in fact i need to check them with somecond(). There will not be an infinite loop since at some point all elements will be smaller than a predefined .epsilon and no more elements can be added.
WesDec
@WesDec: How exactly does that work? Do you remove the elements that meet the condition? Does the condition change? Maybe you could describe your problem a little more detailed (preferably edit your question) so we can provide more specific help.
Space_C0wb0y
A: 

Access your list elements directly by i. Then you can append to your list:

for i in xrange(len(myarr)):
    if somecond(a[i]):
        myarr.append(newObj())
eumiro
@Justin Peel - you were faster with this solution.
eumiro
+3  A: 

You can do this.

bonus_rows = []
for a in myarr:
  if somecond(a):
      bonus_rows.append(newObj())
myarr.extend( bonus_rows )
S.Lott
@S.Lott: This is no good since i also need to check the objects in the bonus_rows, and if somecond() is true for some of those i need to create new objects for those also.
WesDec
@WesDec: That's why you have "nested" loops. Surround all of this in a larger loop.
S.Lott
@WesDec: or stop using a simple list and use a tree. This sounds like breadth-first search, for which a list is the wrong structure.
S.Lott
A: 

You could use the islice from itertools to create an iterator over a smaller portion of the list. Then you can append entries to the list without impacting the items you're iterating over:

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

Even better, you don't even have to iterate over all the elements. You can increment a step size.

wheaties
A: 

Expanding S.Lott's answer so that new items are processed as well:

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

The final list is in done.

Mike DeSimone