-2

I'd like to iterate over lists in python two by two. E.g.

l = [1, 2, 3, 4, 5, 6, 7, 8]
for a, b in magic_function_I_always_wanted(l)
    print(a, b)

>>> 1 2
>>> 2 3
>>> 3 4
>>> 4 5
>>> etc...

I know you can do this in python as well as nearly all languages, I would just like a version which isn't terrible.

I'm curious about all languages but personally care most about python. If there's a way to do this nicely in python (something I haven't thought of which uses tons of izip or something else equally wacky) please answer!

Edits:

This is not a duplicate of the posted question, since that question asks for l -> (l0, l1), (l2, l3), etc... which is easy. I'm asking for l -> (l0, l1), (l1, l2), etc...

Community
  • 1
  • 1
Alex Lenail
  • 12,992
  • 10
  • 47
  • 79
  • @Robert, I'm asking if such a function already exists, or if there's some elegant way to write such a function in python. – Alex Lenail Jul 31 '16 at 23:18
  • @Jonathan, I agree this is a library-level issue, which is why the tag I use is python-itertools. The question you marked this as a duplicate of (which it is not, by the way) is also a library question. – Alex Lenail Jul 31 '16 at 23:18
  • lol your edit... your problem is at least as trivial as that other one. – Stefan Pochmann Jul 31 '16 at 23:43
  • http://stackoverflow.com/a/5394908/901925 gives, as one option the `zip(l,l[1:])` answer. – hpaulj Aug 01 '16 at 00:50
  • Can you make the case that this kind of iteration is so common and natural that it deserves a special construct, one that doesn't build on zip and list slices? Or that those ideas aren't sufficiently expressive? – hpaulj Aug 01 '16 at 09:37
  • @hpaulj I can only speak from experience, but I find that I need this operation regularly, and it's the only one I need which seems not elegantly solved by itertools/collections/functools or the core of the language itself. I don't think there should be a construct for iterating two-by-two specifically, but I wish there were a construct which allowed me to iterate k-by-k without copying the list k times. – Alex Lenail Aug 01 '16 at 14:46
  • In many other languages we'd iterate with `i in range(N)` and access `l[i]` and `l[i+1]`. Or maintain a temporary variable holding `l[i-1]`. Easy `zip` and `slice` is a Python distinctive. – hpaulj Aug 01 '16 at 16:20
  • @hpaulj that's correct, iterating and indexing, while checking for bounds is how you would do this in nearly all cases, and works quickly, but I still wouldn't call that elegant. What I'm looking for is something with the visual simplicity of `zip(l, l[1:])` but the efficiency of `for i in xrange(len(l)-1): l[i], l[i+1]`. If such a thing doesn't exist in python then that's all there is to it. =/ – Alex Lenail Aug 01 '16 at 16:28
  • The other SO cites a `pairwise` recipe in the `itertools/recipes` documentation, https://docs.python.org/3/library/itertools.html#itertools-recipes. That uses `itertools.tee` to make the `l[1:]` iterator. In my time tests it offers a slight speed improvement. – hpaulj Aug 01 '16 at 20:10
  • Ahh. That looks like what I was looking for. Does it make a copy? Depends on how tee is implemented... – Alex Lenail Aug 02 '16 at 13:50
  • Same question in JS https://stackoverflow.com/questions/31973278/iterate-an-array-as-a-pair-current-next-in-javascript – Alex Lenail Apr 10 '18 at 00:29

1 Answers1

3

You can zip the list with a shifted version and then loop through them:

for o, e in zip(l, l[1:]):
    print(o, e)
#(1, 2)
#(2, 3)
#(3, 4)
#(4, 5)
#(5, 6)
#(6, 7)
#(7, 8)

Not sure if this is more elegant or efficient, looks like to create a container like deque is an option:

from collections import deque
n = 3
tmp = deque(l[:(n-1)])
for e in l[(n-1):]:    
    tmp.append(e) 
    print(list(tmp))
    tmp.popleft()
#[1, 2, 3]
#[2, 3, 4]
#[3, 4, 5]
#[4, 5, 6]
#[5, 6, 7]
#[6, 7, 8]
Psidom
  • 209,562
  • 33
  • 339
  • 356
  • 1
    While this solution works, it is slightly problematic because it reallocates two halves of the list. – Nikhil Shinday Jul 31 '16 at 23:07
  • Thanks for the update @Psidom, but as Nikhil mentions, this copies the list twice, which isn't desirable, so I wouldn't call this an elegant solution. – Alex Lenail Jul 31 '16 at 23:23
  • 1
    `zip(l, l[1:])` looks quite elegant to me. It uses standard operations, and be generalized in all kinds of ways, e.g. `izip(l, l[2:], l[1:])`. – hpaulj Aug 01 '16 at 01:14
  • @hpaulj I agree it's visually elegant, but it copies the list, which is painful if you have long lists. You wouldn't do `for x in l[:]` after all, right? – Alex Lenail Aug 01 '16 at 14:47
  • I wouldn't hesitate to iterate on `l[1:]` if I wanted to skip the first value. But I don't work with large enough lists to worry about copies. I work more with `numpy` where `A[1:]` is a `view`, not a copy. Most languages don't have concise slices. In old school `c` we iterate by index or pointer. – hpaulj Aug 01 '16 at 16:15