Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Accessing next/prev element while for looping

5 views
Skip to first unread message

Joseph Garvin

unread,
Dec 18, 2005, 6:23:21 AM12/18/05
to pytho...@python.org
When I first came to Python I did a lot of C style loops like this:

for i in range(len(myarray)):
print myarray[i]

Obviously the more pythonic way is:

for i in my array:
print i

The python way is much more succinct. But a lot of times I'll be looping
through something, and if a certain condition is met, need to access the
previous or the next element in the array before continuing iterating. I
don't see any elegant way to do this other than to switch back to the C
style loop and refer to myarray[i-1] and myarray[i+1], which seems
incredibly silly given that python lists under the hood are linked
lists, presumably having previous/next pointers although I haven't
looked at the interpeter source.

I could also enumerate:

for i, j in enumerate(myarray):
print myarray[i], j # Prints each element twice

And this way I can keep referring to j instead of myarray[i], but I'm
still forced to use myarray[i-1] and myarray[i+1] to refer to the
previous and next elements. Being able to do j.prev, j.next seems more
intuitive.

Is there some other builtin somewhere that provides better functionality
that I'm missing?

Nick Craig-Wood

unread,
Dec 18, 2005, 7:30:06 AM12/18/05
to
Joseph Garvin <k04...@kzoo.edu> wrote:
> When I first came to Python I did a lot of C style loops like this:
>
> for i in range(len(myarray)):
> print myarray[i]
>
> Obviously the more pythonic way is:
>
> for i in my array:
> print i
>
> The python way is much more succinct. But a lot of times I'll be looping
> through something, and if a certain condition is met, need to access the
> previous or the next element in the array before continuing iterating. I
> don't see any elegant way to do this other than to switch back to the C
> style loop and refer to myarray[i-1] and myarray[i+1], which seems
> incredibly silly given that python lists under the hood are linked
> lists, presumably having previous/next pointers although I haven't
> looked at the interpeter source.

I think you'll find python lists are actually arrays not linked
lists...

> I could also enumerate:
>
> for i, j in enumerate(myarray):
> print myarray[i], j # Prints each element twice
>
> And this way I can keep referring to j instead of myarray[i], but I'm
> still forced to use myarray[i-1] and myarray[i+1] to refer to the
> previous and next elements. Being able to do j.prev, j.next seems more
> intuitive.

Boundary conditions are a perenial problem in this sort of thing, what
to do at the start / end of the list...

> Is there some other builtin somewhere that provides better functionality
> that I'm missing?

I suppose you could use itertools...

>>> from itertools import *
>>> L=range(10)
>>> (L1, L2, L3) = tee(L, 3)
>>> L2.next()
0
>>> L3.next()
0
>>> L3.next()
1
>>> for prev, current, next in izip(L1, L2, L3): print prev, current, next
...
0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
>>>

--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick

Steven D'Aprano

unread,
Dec 18, 2005, 7:36:29 AM12/18/05
to
On Sun, 18 Dec 2005 04:23:21 -0700, Joseph Garvin wrote:

> When I first came to Python I did a lot of C style loops like this:
>
> for i in range(len(myarray)):
> print myarray[i]
>
> Obviously the more pythonic way is:
>
> for i in my array:
> print i
>
> The python way is much more succinct. But a lot of times I'll be looping
> through something, and if a certain condition is met, need to access the
> previous or the next element in the array before continuing iterating.

If you need to do that, then either you need to rethink your algorithm so
that you only need to access one element at a time, or you can to change
your loop so that you aren't accessing one element at a time.

For example, you might grab elements three at a time, and if the middle
one meets a certain condition process them, and if not, do nothing.

There is no shortage of ways to handle your situation. However, I can't
help but feel that you probably need to rethink what you are trying to do.
I can't speak for others, but I've never come upon a situation where I
needed to access the element before and the element after the current one.

[thinks...] Wait, no, there was once, when I was writing a parser that
iterated over lines. I needed line continuations, so if the line ended
with a backslash, I needed to access the next line (and potentially the
line after that, and so forth indefinitely). I dealt with that by keeping
a cache of lines seen, adding to the cache if the line ended with a
backslash.

> I
> don't see any elegant way to do this other than to switch back to the C
> style loop and refer to myarray[i-1] and myarray[i+1], which seems
> incredibly silly given that python lists under the hood are linked
> lists, presumably having previous/next pointers although I haven't
> looked at the interpeter source.

Python lists aren't linked lists? They are arrays.


> I could also enumerate:
>
> for i, j in enumerate(myarray):
> print myarray[i], j # Prints each element twice

j is a built-in object used to make complex numbers. Or at least it
was, until you rebound it to the current element from myarray. That's bad
practice, but since using complex numbers is rather unusual, one you will
probably get away with.


> And this way I can keep referring to j instead of myarray[i], but I'm
> still forced to use myarray[i-1] and myarray[i+1] to refer to the
> previous and next elements. Being able to do j.prev, j.next seems more
> intuitive.

But that can't work, because j has no knowledge of the list it is part of:

myarray = ["spam", "parrot", "Spanish Inquisition"]
when i = 1, j = "parrot"

Since j is a string, it has no prev or next methods. How could "parrot"
possibly know that "spam" was the previous element?

You can't even have prev and next methods of the list myarray, because it
has no concept of the current element. The current element i is known by
the iterator used by the for loop, not the list it is iterating over.

You could possibly sub-class list, turning it into an iterator-like object
with next and prev methods, but be aware that "next" method has special
meaning to iterators.

You can probably create a generator or iterator that will do what you
want, but I suspect it won't be exactly straightforward.


--
Steven.

Steven D'Aprano

unread,
Dec 18, 2005, 7:39:00 AM12/18/05
to
On Sun, 18 Dec 2005 23:36:29 +1100, Steven D'Aprano wrote:

> Python lists aren't linked lists? They are arrays.

[slaps head for the stupid typo]
That should have been a full stop, not question mark. Python lists are not
linked lists, period.


--
Steven.

Max Erickson

unread,
Dec 18, 2005, 7:50:20 AM12/18/05
to
j> is a built-in object used to make complex numbers. Or at least it
>was, until you rebound it to the current element from myarray. That's bad
>practice, but since using complex numbers is rather unusual, one you will
>probably get away with.

Is it?

>>> j

Traceback (most recent call last):
File "<pyshell#0>", line 1, in -toplevel-
j
NameError: name 'j' is not defined
>>> 1 + 2j
(1+2j)
>>> j=4
>>> 1 + 2j
(1+2j)
>>>

I am actually curious, as it doesn't appear to be, but you are usually
'right' when you say such things...

Max

Bas

unread,
Dec 18, 2005, 7:58:39 AM12/18/05
to
Just make a custom generator function:

>>> def prevcurnext(seq):
it = iter(seq)
prev = it.next()
cur = it.next()
for next in it:
yield (prev,cur,next)
prev,cur = cur, next


>>> for (a,b,c) in prevcurnext(range(10)):
print a,b,c


0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9

Cheers,
Bas

Steven D'Aprano

unread,
Dec 18, 2005, 7:58:22 AM12/18/05
to
On Sun, 18 Dec 2005 04:50:20 -0800, Max Erickson wrote:

>>j is a built-in object used to make complex numbers. Or at least it
>>was, until you rebound it to the current element from myarray. That's bad
>>practice, but since using complex numbers is rather unusual, one you will
>>probably get away with.
>
> Is it?

[snip example]

Well well, I'll be a monkey's uncle. You learn something new everyday.

Okay, I was wrong. There is no conflict between using j as a name and the
built-in j-as-syntax-for-complex-numbers, except for any potential
conflict in the programmers head.

That makes me feel a lot less guilty about all the times I wrote for j in
range loops :-)


--
Steven.

Bengt Richter

unread,
Dec 18, 2005, 8:23:12 AM12/18/05
to

I don't know of a builtin, but you could make an iterator that gives you
your sequence as (prev, curr, next) item tuples, where curr is the normal
single item in for curr in my_array: ... e.g.,

>>> def pcniter(seq, NULL=NotImplemented):
... seqiter = iter(seq)
... prev = curr = NULL
... try: next = seqiter.next()
... except StopIteration: return
... for item in seqiter:
... prev, curr, next = curr, next, item
... yield prev, curr, next
... yield curr, next, NULL
...
>>> for prev, curr, next in pcniter('abcdef', '\x00'): print '%8r'*3 %( prev, curr, next)
...
'\x00' 'a' 'b'
'a' 'b' 'c'
'b' 'c' 'd'
'c' 'd' 'e'
'd' 'e' 'f'
'e' 'f' '\x00'
>>> for prev, curr, next in pcniter(xrange(4), 'NULL' ): print '%8r'*3 %( prev, curr, next)
...
'NULL' 0 1


0 1 2
1 2 3

2 3 'NULL'

If you want to assign back into myarray[i-1] etc, you'd still need to enumerate, but you could
combine with the above it was useful.

Regards,
Bengt Richter

Alex Martelli

unread,
Dec 18, 2005, 11:12:37 AM12/18/05
to
Steven D'Aprano <st...@REMOVETHIScyber.com.au> wrote:

> I can't speak for others, but I've never come upon a situation where I
> needed to access the element before and the element after the current one.
>
> [thinks...] Wait, no, there was once, when I was writing a parser that
> iterated over lines. I needed line continuations, so if the line ended
> with a backslash, I needed to access the next line (and potentially the
> line after that, and so forth indefinitely). I dealt with that by keeping
> a cache of lines seen, adding to the cache if the line ended with a
> backslash.

In Python, that would be an excellent example use case for a generator
able to "bunch up" input items into output items, e.g.:

def bunch_up(seq):
cache = []
for item in seq:
if item.endswith('\\\n'):
cache.append(item[:-2])
else:
yield ''.join(cache) + item
cache = []

if cache:
raise ValueError("extra continuations at end of sequence")

[[or whatever you wish to do instead of raising if the input sequence
anomalously ends with a ``to be continued'' line]].


Alex

Joseph Garvin

unread,
Dec 18, 2005, 9:26:48 PM12/18/05
to pytho...@python.org
Steven D'Aprano wrote:

All the more inexcusable then, because it's even easier to find the
next/previous element in an array! ;)


Thanks all, Bas/Bangt's solutions are very elegant =)

Steve Holden

unread,
Dec 19, 2005, 3:00:40 AM12/19/05
to pytho...@python.org
Yup, the "j" notation is recognised by the lexer, there's no built-in
object.

regards
Steve
--
Steve Holden +44 150 684 7255 +1 800 494 3119
Holden Web LLC www.holdenweb.com
PyCon TX 2006 www.python.org/pycon/

Paul Rubin

unread,
Dec 19, 2005, 3:30:51 AM12/19/05
to
Joseph Garvin <k04...@kzoo.edu> writes:
> And this way I can keep referring to j instead of myarray[i], but I'm
> still forced to use myarray[i-1] and myarray[i+1] to refer to the
> previous and next elements. Being able to do j.prev, j.next seems more
> intuitive.
>
> Is there some other builtin somewhere that provides better
> functionality that I'm missing?

You could use a generator comprehension (untested):

for prev,cur,next in ((myarray[i-1], myarray[i], myarray[i+1])
for i in xrange(1,len(myarray)-1)):
do_something_with (prev, cur, next)

Alternatively (untested):

elts = iter(myarray)
prev,cur,next = elts.next(),elts.next(),elts.next()
for next2 in elts:
do_something_with (prev, cur, next)
prev,cur,next = cur, next, next2

Of course these fail when there's less than 3 elements.

Paul Rubin

unread,
Dec 19, 2005, 3:35:17 AM12/19/05
to
Paul Rubin <http://phr...@NOSPAM.invalid> writes:
> elts = iter(myarray)
> prev,cur,next = elts.next(),elts.next(),elts.next()
> for next2 in elts:
> do_something_with (prev, cur, next)
> prev,cur,next = cur, next, next2
>
> Of course these fail when there's less than 3 elements.

Ehh, too clever for my own good. This fails with exactly 3 elements.
It was a "shortened" version of

elts = iter(myarray)
prev,cur,next = elts.next(),elts.next(),elts.next()

try:
while True:
do_something_with (prev, cur, next)
prev,cur,next = cur, next, elts.next()
except StopIteration: pass

which is messy, and which could get confused by do_something_with...
raising StopIteration for some reason.

Peter Otten

unread,
Dec 19, 2005, 7:15:02 AM12/19/05
to
Bengt Richter wrote:

> >>> def pcniter(seq, NULL=NotImplemented):
> ...     seqiter = iter(seq)
> ...     prev = curr = NULL
> ...     try: next = seqiter.next()
> ...     except StopIteration: return
> ...     for item in seqiter:
> ...         prev, curr, next = curr, next, item
> ...         yield prev, curr, next
> ...     yield curr, next, NULL
> ...


Just a reminder:

"""
In a generator function, the return statement is not allowed to include
an expression_list[3]. In that context, a bare return indicates that the
generator is done and will cause StopIteration to be raised.
"""

try:
# may raise StopIteration
except StopIteration:
return

is the same as

try:
# may raise StopIteration
except StopIteration:
raise StopIteration

and may be simplified to

# may raise StopIteration

Peter

0 new messages