6

I want to know if there's a more Pythonic way of doing the following:

A = some list
i = 0
j = 1
for _ in range(1, len(A)):
    #some operation between A[i] and A[j]
    i += 1
    j += 1

I feel like this should/could be done differently. Ideas?

EDIT: Since some are asking for requirements. I wanted a general-purpose answer. Maybe to check if A[i], A[j] are between a certain range, or if they're equal. Or maybe I wanted to do a "trickle-up" of elements. The more general, the better.

lorenzocastillo
  • 985
  • 3
  • 13
  • 24

5 Answers5

8

There's a nice little recipe in itertools for doing this. As a bonus it works with any iterable, not just sequences.

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)

for current, next_ in pairwise(A):
    # do something
    pass

If you need the index as well then just enumerate the pairwise iterator.

for current_index, (current, next_) in enumerate(pairwise(A)):
    # do something
    pass
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • what are the return values for enumerate. grossly confused. is it index, value. How does enumerate know if it is index, value or value and nextvalue. ? – AAI Sep 18 '17 at 22:01
  • 1
    @Whatever You can check the help for enumerate with `help(enumerate)` which details how enumerate works. It's always yields a sequence 2-tuples of `index, element_at_index`. However, I further unpack `element_at_index` into `current` and `next_` (because that's what `pairwise` produces). – Dunes Sep 20 '17 at 08:40
3

zip lets you combine multiple iterators:

for i,j in zip(range(0,len(A)-1), range(1,len(A))):
    #some operation between A[i] and A[j]

you can also use enumerate on a range object:

for i,j in enumerate(range(1,len(A)):
    #some operation between A[i] and A[j]

Note that unlike the other answers this gives you access to the indices of A not just the items, this is necessary if you want to use any assignment to A[i] or A[j], for example here is a very basic bubble sort:

A = list(range(10))
found1=True
while found1:
    found1=False
    for i,j in enumerate(range(1,len(A))):
        if A[i] < A[j]:
            A[i],A[j] = A[j],A[i]
            found1=True
print(A)

this is only possible when you iterate over the indices of A.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
3

For operation '+':

A = [A[i+1]+A[i] for i in range(len(A)-1)]

General:

A = [operation(A[i], A[i+1]) for i in range(len(A)-1)]
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
manza
  • 322
  • 1
  • 11
2

You could do :

A = some list
for Ai, Aj in zip(A, A[1:]):
    #some operation between A[i] and A[j]
Jacques Gaudin
  • 15,779
  • 10
  • 54
  • 75
0
from itertools import islice
A1 = iter(A)
A2 = islice(A, 1, None)
for a1, a2 in zip(A1, A2):
    # do whatever with A[i], A[i+1]

islice is more generic than using A[1:] since it works on iterators (which are not subscriptable) as well. Please note that you need to pass None as the third parameter because islice(iterable, stop) only returns the first stop items of the iterator (hence the name) which is exactly the opposite of what you want, when a third parameter is passed it treats the second as starting position rather than stopping index.

islice(iterable, start, stop[, step])  # roughly = iterable[start:stop:step]
islice(iterable, stop)                 # roughly = iterable[:stop]

Please refer to the docs for more info.

Now zip() emits values till the shortest iterator(A2) is exhausted. So no worries about the possible IndexError.

Mohammad Jafar Mashhadi
  • 4,102
  • 3
  • 29
  • 49
C Panda
  • 3,297
  • 2
  • 11
  • 11