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

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

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

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

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 очень эффективна. Результат и утешительный, и неожиданный.

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

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

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

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

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])

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

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

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

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

наконец-то

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

5 ответов


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

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)

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

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

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

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

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

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

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


что касается большинства питонов, я бы сказал рецепты, поставляемые в исходных документах 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)