views:

161

answers:

4
li = [0, 1, 2, 3]

running = True
while running:
    for elem in li:
        thiselem = elem
        nextelem = li[li.index(elem)+1]

When this reaches the last element, an IndexError is raised (as is the case for any list, tuple, dictionary, or string that is iterated). I actually want at that point for nextelem to equal li[0]. My rather cumbersome solution to this was

while running:
    for elem in li:
        thiselem = elem
        nextelem = li[li.index(elem)-len(li)+1]   # negative index

Is there a better way of doing this?

+2  A: 
while running:
    for elem,next_elem in zip(li, li[1:]+[li[0]]):
        ...
gnibbler
+1 Simple is sometimes best.
Mark Byers
This is my second favorite, after mine. :-) Simple is best, but if the list is at all large, this creates two copies of it.
Omnifarious
+5  A: 

You can use a pairwise cyclic iterator:

from itertools import izip, cycle, tee

def pairwise(seq):
    a, b = tee(seq)
    next(b)
    return izip(a, b)

for elem, next_elem in pairwise(cycle(li)):
    ...
Ants Aasma
This is good. It incorporates the while loop and the for loop into one concise loop, which continuously iterates over adjacent pairs in the list, and wraps around at the end.
Jason R. Coombs
+1 for using pairwise - an unofficial library function which exists only in the documentation for itertools. So while you can't import it, it's known to work.
Jason R. Coombs
I think obscuring the test for running inside the for loop and requiring the use of `break` isn't such a good idea.
Omnifarious
+4  A: 

After thinking this through carefully, I think this is the best way. It lets you step off in the middle easily without using break, which I think is important, and it requires minimal computation, so I think it's the fastest. It also doesn't require that li be a list or tuple. It could be any iterator.

from itertools import cycle

li = [0, 1, 2, 3]

running = True
licycle = cycle(li)
# Prime the pump
nextelem = licycle.next()
while running:
    thiselem, nextelem = nextelem, licycle.next()

I'm leaving the other solutions here for posterity.

All of that fancy iterator stuff has its place, but not here. Use the % operator.

li = [0, 1, 2, 3]

running = True
while running:
    for idx, elem in enumerate(li):
        thiselem = elem
        nextelem = li[(idx + 1) % len(li)]

Now, if you intend to infinitely cycle through a list, then just do this:

li = [0, 1, 2, 3]

running = True
idx = 0
while running:
    thiselem = li[idx]
    idx = (idx + 1) % len(li)
    nextelem = li[idx]

I think that's easier to understand than the other solution involving tee, and probably faster too. If you're sure the list won't change size, you can squirrel away a copy of len(li) and use that.

This also lets you easily step off the ferris wheel in the middle instead of having to wait for the bucket to come down to the bottom again. The other solutions (including yours) require you check running in the middle of the for loop and then break.

Omnifarious
+1 Yeah, this is also simple and effective. There's no need for these unnecessarily complex functional solutions to problems which have extremely simple procedural solutions. Python is not a functional language!
Mark Byers
@Mark Byers, thanks! I do think there's room for a fairly functional solution if the list is fairly small, and I added that. The whole bit with `running` is not functional, and trying to shoehorn it into a functional solution is ugly.
Omnifarious
@Mark Byers: There, the answer I think is best uses itertools, but in a very minimalistic kind of way. I think its very fast, and easy to understand.
Omnifarious
I'm such a perfectionist. It's irritating that after 8 edits it becomes a community wiki answer. *sigh*
Omnifarious
Aw, that sucks. But thanks for the effort! I think your three solutions are my favourite.
A: 
while running:
    lenli = len(li)
    for i, elem in enumerate(li):
        thiselem = elem
        nextelem = li[(i+1)%lenli]
Ned Batchelder