Bob
Bob

Reputation: 10815

pairwise traversal of a list or tuple

a = [5, 66, 7, 8, 9, ...]

Is it possible to make an iteration instead of writing like this?

a[1] - a[0]

a[2] - a[1]

a[3] - a[2]

a[4] - a[3]

...

Thank you!

Upvotes: 30

Views: 36912

Answers (6)

tokland
tokland

Reputation: 67900

It's ok to use range. However, programming (like maths) is about building on abstractions. Consecutive pairs [(x0, x1), (x1, x2), ..., (xn-2, xn-1)], are called pairwise combinations. See an example in the itertools docs. Once you have this function in your toolset, you can write:

for x, y in pairwise(xs):
    print(y - x)

Or used as a generator expression:

consecutive_diffs = (y - x for (x, y) in pairwise(xs))

Upvotes: 67

Andrey Semakin
Andrey Semakin

Reputation: 2775

I would recommend to use awesome more_itertools library, it has ready-to-use pairwise function:

import more_itertools

for a, b in more_itertools.pairwise([1, 2, 3, 4, 5]):
    print(a, b)
# 1 2
# 2 3
# 3 4
# 4 5

It will save you from writing your own (likely buggy) implementation. For example, most of implementations on this page don't handle the case with empty iterable correctly -- generator function should never raise StopIteration, this behavior considered deprecated and causes DeprecationWarning in Python 3.6. It won't work in Python 3.7 at all.

Upvotes: 6

grubberr
grubberr

Reputation: 47

def pairwise(iterable):
    i = iter(iterable)
    while True:
        yield next(i), next(i, '')

Upvotes: -1

oz123
oz123

Reputation: 28868

Here is the example from the itertools reciepes:

from itertools import tee

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

Which is not very readable.If you prefer something more understandable and understand how generators work, here a bit longer example with the same result:

def pairwise(it):
    """
    Walk a list in overlapping pairs.

    >>> list(pairwise([0, 1, 2, 3, 4, 5]))
    [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
    """
    it = iter(it)
    start = None
    while True:
        if not start:
            start = next(it)
        end = next(it)
        yield start, end
        start = end

Upvotes: 2

aaronasterling
aaronasterling

Reputation: 71044

for a small list in python 2 or any list in python 3, you can use

[x - y for x, y in zip(a[1:], a)]

for a larger list, you probably want

import itertools as it

[x - y for x, y in it.izip(a[1:], a)]

if you are using python 2

And I would consider writing it as a generator expression instead

(x - y for x, y in it.izip(a[1:], a))

This will avoid creating the second list in memory all at once but you will only be able to iterate over it once. If you only want to iterate over it once, then this is ideal and it's easy enough to change if you decide later that you need random or repeated access. In particular if you were going to further process it to make a list, then this last option is ideal.

update:

The fastest method by far is

import itertools as it
import operator as op

list(it.starmap(op.sub, it.izip(a[1:], a)))

$ python -mtimeit -s's = [1, 2]*10000' '[x - y for x, y in zip(s[1:], s)]'
100 loops, best of 3: 13.5 msec per loop

$ python -mtimeit -s'import itertools as it; s = [1, 2]*10000' '[x - y for x, y in it.izip(s[1:], s)]'
100 loops, best of 3: 8.4 msec per loop

$ python -mtimeit -s'import itertools as it; import operator as op; s = [1, 2]*10000' 'list(it.starmap(op.sub, it.izip(s[1:], s)))'
100 loops, best of 3: 6.38 msec per loop

Upvotes: 23

Ivo van der Wijk
Ivo van der Wijk

Reputation: 16785

Sure.

for i in range(1, len(a)):
    print a[i] - a[i-1]

I fail to see what the real problem is here. Have you read the python tutorial?

Upvotes: 2

Related Questions