Iterating over every two elements in a list

Posted on

Solving problem is about exposing yourself to as many situations as possible like Iterating over every two elements in a list and practice these strategies over and over. With time, it becomes second nature and a natural way you approach any problems in general. Big or small, always start with a plan, use other strategies mentioned here till you are confident and ready to code the solution.
In this post, my aim is to share an overview the topic about Iterating over every two elements in a list, which can be followed any time. Take easy to follow this discuss.

Iterating over every two elements in a list

How do I make a for loop or a list comprehension so that every iteration gives me two elements?

l = [1,2,3,4,5,6]
for i,k in ???:
    print str(i), '+', str(k), '=', str(i+k)

Output:

1+2=3
3+4=7
5+6=11
Asked By: jackhab

||

Answer #1:

You need a pairwise() (or grouped()) implementation.

For Python 2:

from itertools import izip
def pairwise(iterable):
    "s -> (s0, s1), (s2, s3), (s4, s5), ..."
    a = iter(iterable)
    return izip(a, a)
for x, y in pairwise(l):
   print "%d + %d = %d" % (x, y, x + y)

Or, more generally:

from itertools import izip
def grouped(iterable, n):
    "s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ..."
    return izip(*[iter(iterable)]*n)
for x, y in grouped(l, 2):
   print "%d + %d = %d" % (x, y, x + y)

In Python 3, you can replace izip with the built-in zip() function, and drop the import.

All credit to martineau for his answer to my question, I have found this to be very efficient as it only iterates once over the list and does not create any unnecessary lists in the process.

N.B: This should not be confused with the pairwise recipe in Python’s own itertools documentation, which yields s -> (s0, s1), (s1, s2), (s2, s3), ..., as pointed out by @lazyr in the comments.

Little addition for those who would like to do type checking with mypy on Python 3:

from typing import Iterable, Tuple, TypeVar
T = TypeVar("T")
def grouped(iterable: Iterable[T], n=2) -> Iterable[Tuple[T, ...]]:
    """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), ..."""
    return zip(*[iter(iterable)] * n)
Answered By: Johnsyweb

Answer #2:

Well you need tuple of 2 elements, so

data = [1,2,3,4,5,6]
for i,k in zip(data[0::2], data[1::2]):
    print str(i), '+', str(k), '=', str(i+k)

Where:

  • data[0::2] means create subset collection of elements that (index % 2 == 0)
  • zip(x,y) creates a tuple collection from x and y collections same index elements.
Answered By: Margus

Answer #3:

>>> l = [1,2,3,4,5,6]
>>> zip(l,l[1:])
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
>>> zip(l,l[1:])[::2]
[(1, 2), (3, 4), (5, 6)]
>>> [a+b for a,b in zip(l,l[1:])[::2]]
[3, 7, 11]
>>> ["%d + %d = %d" % (a,b,a+b) for a,b in zip(l,l[1:])[::2]]
['1 + 2 = 3', '3 + 4 = 7', '5 + 6 = 11']
Answered By: pyanon

Answer #4:

A simple solution.

l = [1, 2, 3, 4, 5, 6]
for i in range(0, len(l), 2):
    print str(l[i]), '+', str(l[i + 1]), '=', str(l[i] + l[i + 1])
Answered By: taskinoor

Answer #5:

While all the answers using zip are correct, I find that implementing the functionality yourself leads to more readable code:

def pairwise(it):
    it = iter(it)
    while True:
        try:
            yield next(it), next(it)
        except StopIteration:
            # no more elements in the iterator
            return

The it = iter(it) part ensures that it is actually an iterator, not just an iterable. If it already is an iterator, this line is a no-op.

Usage:

for a, b in pairwise([0, 1, 2, 3, 4, 5]):
    print(a + b)
Answered By: mic_e

Answer #6:

I hope this will be even more elegant way of doing it.

a = [1,2,3,4,5,6]
zip(a[::2], a[1::2])
[(1, 2), (3, 4), (5, 6)]
Answered By: Vivek Srinivasan

Answer #7:

In case you’re interested in the performance, I did a small benchmark (using my library simple_benchmark) to compare the performance of the solutions and I included a function from one of my packages: iteration_utilities.grouper

from iteration_utilities import grouper
import matplotlib as mpl
from simple_benchmark import BenchmarkBuilder
bench = BenchmarkBuilder()
@bench.add_function()
def Johnsyweb(l):
    def pairwise(iterable):
        "s -> (s0, s1), (s2, s3), (s4, s5), ..."
        a = iter(iterable)
        return zip(a, a)
    for x, y in pairwise(l):
        pass
@bench.add_function()
def Margus(data):
    for i, k in zip(data[0::2], data[1::2]):
        pass
@bench.add_function()
def pyanon(l):
    list(zip(l,l[1:]))[::2]
@bench.add_function()
def taskinoor(l):
    for i in range(0, len(l), 2):
        l[i], l[i+1]
@bench.add_function()
def mic_e(it):
    def pairwise(it):
        it = iter(it)
        while True:
            try:
                yield next(it), next(it)
            except StopIteration:
                return
    for a, b in pairwise(it):
        pass
@bench.add_function()
def MSeifert(it):
    for item1, item2 in grouper(it, 2):
        pass
bench.use_random_lists_as_arguments(sizes=[2**i for i in range(1, 20)])
benchmark_result = bench.run()
mpl.rcParams['figure.figsize'] = (8, 10)
benchmark_result.plot_both(relative_to=MSeifert)

enter image description here

So if you want the fastest solution without external dependencies you probably should just use the approach given by Johnysweb (at the time of writing it’s the most upvoted and accepted answer).

If you don’t mind the additional dependency then the grouper from iteration_utilities will probably be a bit faster.

Additional thoughts

Some of the approaches have some restrictions, that haven’t been discussed here.

For example a few solutions only work for sequences (that is lists, strings, etc.), for example Margus/pyanon/taskinoor solutions which uses indexing while other solutions work on any iterable (that is sequences and generators, iterators) like Johnysweb/mic_e/my solutions.

Then Johnysweb also provided a solution that works for other sizes than 2 while the other answers don’t (okay, the iteration_utilities.grouper also allows setting the number of elements to “group”).

Then there is also the question about what should happen if there is an odd number of elements in the list. Should the remaining item be dismissed? Should the list be padded to make it even sized? Should the remaining item be returned as single? The other answer don’t address this point directly, however if I haven’t overlooked anything they all follow the approach that the remaining item should be dismissed (except for taskinoors answer – that will actually raise an Exception).

With grouper you can decide what you want to do:

>>> from iteration_utilities import grouper
>>> list(grouper([1, 2, 3], 2))  # as single
[(1, 2), (3,)]
>>> list(grouper([1, 2, 3], 2, truncate=True))  # ignored
[(1, 2)]
>>> list(grouper([1, 2, 3], 2, fillvalue=None))  # padded
[(1, 2), (3, None)]
Answered By: MSeifert

Answer #8:

Use the zip and iter commands together:

I find this solution using iter to be quite elegant:

it = iter(l)
list(zip(it, it))
# [(1, 2), (3, 4), (5, 6)]

Which I found in the Python 3 zip documentation.

it = iter(l)
print(*(f'{u} + {v} = {u+v}' for u, v in zip(it, it)), sep='n')
# 1 + 2 = 3
# 3 + 4 = 7
# 5 + 6 = 11

To generalise to N elements at a time:

N = 2
list(zip(*([iter(l)] * N)))
# [(1, 2), (3, 4), (5, 6)]
Answered By: Quantum Mechanic

Leave a Reply

Your email address will not be published.