26
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!

aaronasterling
  • 68,820
  • 20
  • 127
  • 125
Bob
  • 10,427
  • 24
  • 63
  • 71

6 Answers6

65

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))
tokland
  • 66,169
  • 13
  • 144
  • 170
  • 1
    Python 3.9.7 >>> from itertools import pairwise Traceback (most recent call last): File "", line 1, in ImportError: cannot import name 'pairwise' from 'itertools' (unknown location) – Osvald Laurits Sep 17 '21 at 09:11
  • 1
    pairwise has since (later than 3.1 or so) been moved to receipt. Use zip and tee per official docs. – Martin Dec 01 '21 at 06:27
21

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
aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • 8
    they actually look overly complicated for a trivial task, especially considering the OP's assumed experience. When did ordinary for-loops become a bad thing to use? – Ivo van der Wijk Oct 03 '10 at 12:10
  • 6
    when the became twice as slow and cruftier to read than a comprehension. – aaronasterling Oct 03 '10 at 12:13
  • Ok, I profiled and in this case they're not twice as slow but they're still the slowest by far. 16.8 msec with the same input as I display above. and my point about them being cruftier to read still stands. – aaronasterling Oct 03 '10 at 12:24
  • 3
    @Ivo: Iterating over the indices instead of the elements of an array also introduces additional possibilities for mistakes. For example if you accidentally let your for-loop start at 0 instead of 1, you'd get an index out of bounds error. This is not something you have to worry about using list comprehensions and itertools. – sepp2k Oct 03 '10 at 12:53
  • in spite of the bug risk in iterating over indices, it's by far the fastest in pypy 2.0 (250 usec per vs 400-600usec per for the others): `pypy -mtimeit -s'import itertools;import operator; seq=[0,1]*10000' '[seq[i+1] - seq[i] for i in range(len(seq)-1)]'` – Jonathan Graehl Jun 27 '13 at 19:40
4

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.

Andrey Semakin
  • 2,032
  • 1
  • 23
  • 50
  • 2
    In Python 3.10, a [`pairwise` function](https://docs.python.org/3/library/itertools.html#itertools.pairwise) has been added to `itertools` which is part of the standard library. – Trenton Oct 08 '21 at 20:14
2

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
oz123
  • 27,559
  • 27
  • 125
  • 187
1

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?

Ivo van der Wijk
  • 16,341
  • 4
  • 43
  • 57
-1
def pairwise(iterable):
    i = iter(iterable)
    while True:
        yield next(i), next(i, '')
grubberr
  • 47
  • 3
  • 1
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – Donald Duck Jul 15 '17 at 18:09
  • −1. For `[1, 2, 3, 4, 5, 6]` this yields `[(1, 2), (3, 4), (5, 6)]`. The question wants something closer to `[(1, 2), (2, 3), (3, 4), ...]`. – Alex Peters Apr 03 '21 at 13:09