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

Достаточно часто я обнаружил необходимость обрабатывать список парами. Мне было интересно, какой будет питонский и эффективный способ сделать это, и нашел это в 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() ушел

10 ответов

Решение

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

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)

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

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

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

Что касается большинства 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)
>>> 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)]

Только сделай это:

>>> l = [1, 2, 3, 4, 5, 6]
>>> [(x,y) for x,y in zip(l[:-1], l[1:])]
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

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

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

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

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

Вот пример создания пар / ног с помощью генератора. Генераторы свободны от ограничений стека

def pairwise(data):
    zip(data[::2], data[1::2])

Пример:

print(list(pairwise(range(10))))

Выход:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Этот фрагмент работал для меня. Создает пары кортежей и добавляет к последней паре пустую строку, если длина списка нечетная ( fillvalue="").

      zip_longest(*[iter(my_list)] * 2, fillvalue="")

# odd
list(zip_longest(*[iter([0, 1, 2, 3, 4, 5, 6])] * 2, fillvalue=""))
[(0, 1), (2, 3), (4, 5), (6, '')]

# even
list(zip_longest(*[iter([0, 1, 2, 3, 4, 5])] * 2, fillvalue=""))
[(0, 1), (2, 3), (4, 5)]

На всякий случай, если кому-то понадобится алгоритм ответа, вот он:

>>> def getPairs(list):
...     out = []
...     for i in range(len(list)-1):
...         a = list.pop(0)
...         for j in a:
...             out.append([a, j])
...     return b
>>> 
>>> k = [1, 2, 3, 4]
>>> l = getPairs(k)
>>> l
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

Но обратите внимание, что ваш исходный список также будет уменьшен до последнего элемента, потому что вы использовали pop в теме.

>>> k
[4]
Другие вопросы по тегам