Welcome, guest | Sign In | My Account | Store | Cart

Sometimes it's useful to iterate through a series of items, looking at an item and the next one.

Python, 21 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def two_finger(iterable):
    """
    >>> sequence = [1, 3, 9, 5, 22, 11]
    >>> for item, next in two_finger(sequence):
    ...     print item, next
    ... 
    1 3
    3 9
    9 5
    5 22
    22 11
    """
    iterator = iter(iterable)
    item = iterator.next()
    while True:
        try:
            next = iterator.next()
            yield item, next
            item = next
        except StopIteration:
            return

8 comments

Brian guertin 11 years, 10 months ago  # | flag

What about:

seq = [1, 3, 9, 5, 22, 11]
for item, next in zip(seq, seq[1:]):
  print item, next
Brian guertin 11 years, 10 months ago  # | flag

Don't use my way if iterating has side effects however

Jan Kaliszewski 11 years, 10 months ago  # | flag

For longer seq i'd rather do that: (in Python 2.x)

from itertools import izip, islice
for item, next_item in izip(seq, islice(seq, 1, None)):
    print item, next_item  # next() is a builtin function (since Python 2.6)

But we still iterate over it twice.

So (in case of long seqences) the recipe by Karl is better.

You can also express the same idea in such a way:

iterator = iter(seq)
item = next(iterator)
for next_item in iterator:
    print item, next_item  # or anything else...
    item = next_item
Karl Dickman (author) 11 years, 10 months ago  # | flag

I like Jan's final suggestion. I've modified my own copy to:

def two_finger(iterable):
    iterator = iter(iterable)
    item = next(iterator)
    for next_item in iterator:
        yield item, next_item
        item = next_item
Karl Dickman (author) 11 years, 10 months ago  # | flag

The other problem with the code in comment #1 is that it really only works for lists. You can't use it with, say, xrange. Jan's first alternative works for sequences, but not for generators. If you use generators, you have to use islice(seq, 0, None)--but then it won't work with indexable sequences.

Jan Kaliszewski 11 years, 10 months ago  # | flag

Jan's first alternative works for sequences, but not for generators. If you use generators, you have to use islice(seq, 0, None)

Neither islice(seq, 1, None) nor islice(seq, 0, None) do the task correctly using generators (and generally iterators). Of course, not because of using islice (it works with generators very well), but because the same iterator object is used twice by izip.

To have a universal solution this way, we'd use itertools.tee to split iterator into two, e.g.:

from itertools import izip, islice, tee
it1, it2 = tee(seq)
for item, next_item in izip(it1, islice(it2, 1, None)):
    print item, next_item

Though I'd preffer the solution with one iterator and item = next_item.

Matteo Dell'Amico 11 years, 10 months ago  # | flag

From the documentation of itertools ("Recipes" section, which is a very fine read):

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)
Karl Dickman (author) 11 years, 10 months ago  # | flag

Leave it to the people who wrote the language to come up with the best version. :)

Created by Karl Dickman on Sat, 9 Jan 2010 (MIT)
Python recipes (4591)
Karl Dickman's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks