Пары из единого списка - PullRequest
       30

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

81 голосов
/ 07 января 2011

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

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

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

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

Результаты на данный момент

Наиболее питонны и очень эффективны:

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

Наиболее эффективны и очень питонны:

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

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

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

Наконец

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

Ответы [ 6 ]

40 голосов
/ 07 января 2011

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

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)

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

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)
36 голосов
/ 07 января 2011

Я бы сказал, что ваше первоначальное решение 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)]
6 голосов
/ 09 января 2011

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

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)
3 голосов
/ 04 мая 2015

Что касается большинства 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)
2 голосов
/ 07 января 2011

Есть ли другой, "лучший" способ обхода списка в парах?

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

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

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

0 голосов
/ 19 февраля 2019
>>> 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)]
...