Пары из одного списка

Достаточно часто я нашел необходимость обрабатывать список по парам. Мне было интересно, какой будет питонический и эффективный способ сделать это, и нашел это в Google:

pairs = zip(t[::2], t[1::2])

Я думал, что это было достаточно pythonic, но после недавнего обсуждения идиом против эффективности я решил сделать несколько тестов:

import time
from itertools import islice, izip

def pairs_1(t):
    return zip(t[::2], t[1::2]) 

def pairs_2(t):
    return izip(t[::2], t[1::2]) 

def pairs_3(t):
    return izip(islice(t,None,None,2), islice(t,1,None,2))

A = range(10000)
B = xrange(len(A))

def pairs_4(t):
    # ignore value of t!
    t = B
    return izip(islice(t,None,None,2), islice(t,1,None,2))

for f in pairs_1, pairs_2, pairs_3, pairs_4:
    # time the pairing
    s = time.time()
    for i in range(1000):
        p = f(A)
    t1 = time.time() - s

    # time using the pairs
    s = time.time()
    for i in range(1000):
        p = f(A)
        for a, b in p:
            pass
    t2 = time.time() - s
    print t1, t2, t2-t1

Это были результаты на моем компьютере:

1.48668909073 2.63187503815 1.14518594742
0.105381965637 1.35109519958 1.24571323395
0.00257992744446 1.46182489395 1.45924496651
0.00251388549805 1.70076990128 1.69825601578

Если я правильно их интерпретирую, это должно означать, что реализация списков, индексация списков и разбиение списков в Python очень эффективны. Это результат как утешительный, так и неожиданный.

Есть ли другой "лучший" способ перемещения списка попарно?

Обратите внимание, что если список имеет нечетное число элементов, последний не будет ни в одной из пар.

Каким будет правильный способ обеспечить включение всех элементов?

Я добавил эти два предложения из ответов на тесты:

def pairwise(t):
    it = iter(t)
    return izip(it, it)

def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Вот результаты:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Результаты пока

Большинство pythonic и очень эффективны:

pairs = izip(t[::2], t[1::2])

Самый эффективный и очень pythonic:

pairs = izip(*[iter(t)]*2)

Мне потребовалось время, чтобы понять, что первый ответ использует два итератора, а второй использует один.

Чтобы иметь дело с последовательностями с нечетным числом элементов, было предложено увеличить исходную последовательность, добавляя один элемент (None), который соединяется с предыдущим последним элементом, что может быть достигнуто с помощью itertools.izip_longest().

Наконец

Обратите внимание, что в Python 3.x, zip() ведет себя как itertools.izip(), а itertools.izip() отсутствует.

Ответ 1

Мой любимый способ сделать это:

from itertools import izip

def pairwise(t):
    it = iter(t)
    return izip(it,it)

# for "pairs" of any length
def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Если вы хотите соединить все элементы, вам, очевидно, может понадобиться значение fillvalue:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)

Ответ 2

Я бы сказал, что ваше начальное решение pairs = zip(t[::2], t[1::2]) является лучшим, потому что его легче всего читать (а в Python 3, zip автоматически возвращает итератор вместо списка).

Чтобы обеспечить включение всех элементов, вы можете просто расширить список на None.

Затем, если список имеет нечетное число элементов, последняя пара будет (item, None).

>>> t = [1,2,3,4,5]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, None)]
>>> t = [1,2,3,4,5,6]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, 6)]

Ответ 3

Я начинаю с небольшого отказа от ответственности - не используйте приведенный ниже код. Это совсем не Pythonic, я написал просто для удовольствия. Он похож на функцию @THC4k pairwise, но использует закрытие iter и lambda. Он не использует модуль itertools и не поддерживает fillvalue. Я выразился здесь, потому что кому-то может показаться интересным:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)

Ответ 4

Что касается большинства pythonic, я бы сказал рецепты, поставляемые в исходных документах python (некоторые из них очень похожи на ответы, предоставленные @JochenRitzel), вероятно, ваш лучший выбор;)

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Ответ 5

Есть ли другой "лучший" способ перемещения списка в пары?

Я не могу сказать точно, но я сомневаюсь в этом: любой другой обход будет включать в себя более код Python, который должен быть интерпретирован. Встроенные функции, такие как zip(), записываются на C, что намного быстрее.

Каким будет правильный способ обеспечить включение всех элементов?

Проверьте длину списка, и если он нечетный (len(list) & 1 == 1), скопируйте список и добавьте элемент.

Ответ 6

>>> my_list = [1,2,3,4,5,6,7,8,9,10]
>>> my_pairs = list()
>>> while(my_list):
...     a = my_list.pop(0); b = my_list.pop(0)
...     my_pairs.append((a,b))
... 
>>> print(my_pairs)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]