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


98

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


RE: "правильний шлях" - немає "правильного" шляху! Це залежить від випадку використання.
Ендрю Яффе

@Andrew Jaffe Я дав критерії "найкращого" в цьому випадку: ефективний і пітонічний.
Апалала

@Apalala: Я маю на увазі, що результат використання непарного числа залежить від використання. Наприклад: ви можете просто залишити останній елемент, або додати певний відомий фіктивний елемент, або дублювати останній
Ендрю Яффе

2
@Apalala: тому що ти використовуєш кілька mumbo-jumbo замість timeitмодуля.
SilentGhost

1
н-продубльовані: тільки в швидкий пошук: stackoverflow.com/questions/4501636 , stackoverflow.com/questions/4170295 , stackoverflow.com/questions/434287
tokland

Відповіді:


52

Мій улюблений спосіб зробити це:

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)

Перша (попарно) функція, здається, не вистачає клонування та просування другого ітератора. Дивіться itertoolsрозділ рецептів.
Апалала

@Apalala: zip пересуває той самий ітератор двічі.
Йохен Рітцель

Звичайно, ти прав, і попарно є найбільш ефективним поки що, я не знаю чому.
Апалала

1
Мені подобається це рішення: воно ліниве, і воно чудово спрацьовує з позитивом ітераторів. Ви навіть можете зробити його однолінійним, хоча можливо за рахунок читабельності:izip(*[iter(t)]*size)
Ченнінг Мур,

Чи не хочете, щоб у зв'язку з вашим другим рішенням уникнути створення списку, якщо будете працювати після виконання?
макс

40

Я б сказав, що ваше початкове рішення 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

Я починаю з невеликої відмови від відповідальності - не використовуйте наведений нижче код. Це зовсім не піфонічне, я писав просто заради розваги. Це схоже на pairwiseфункцію @ THC4k, але вона використовує iterта закриває lambda. Він не використовує itertoolsмодуль і не підтримує fillvalue. Я ставлю його сюди, тому що комусь це може бути цікавим:

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

3

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

Чи існує інший, «кращий» спосіб перегляду списку парами?

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

Який би був правильний спосіб забезпечити включення всіх елементів?

Перевірте довжину списку і, якщо це непарно ( len(list) & 1 == 1), скопіюйте список та додайте елемент.


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

IndexError:
випливає

@HQuser Звичайно, ви отримаєте цю помилку, якщо у списку є непарна кількість елементів. Ви повинні точно знати, що у вас є пари або перевірити наявність цієї помилки.
WaterMolecule


0

Ось приклад створення пар / ніг за допомогою генератора. Генератори не містять обмежень на стек

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

Приклад:

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

Вихід:

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

Порівняння часу виконання?
Алан

Список не розбивається на пари, оскільки більшість номерів у вихідному списку відображається у двох кортежах. Очікуваний вихід[(0, 1), (2, 3), (4, 5)....
Апалала

@Apalala дякую, що вказали. Я виправив код, щоб забезпечити правильний вихід
Влад Безден

zip()вже повертає генератор в Python 3.x, @VladBezden
Apalala

-1

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

>>> 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.