Pary z jednej listy

100

Często znajdowałem potrzebę przetwarzania listy parami. Zastanawiałem się, jaki byłby pythonowy i skuteczny sposób na zrobienie tego, i znalazłem to w Google:

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

Wydawało mi się, że to dość pythonowe, ale po niedawnej dyskusji na temat idiomów i wydajności zdecydowałem się na kilka testów:

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

Oto wyniki na moim komputerze:

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

Jeśli interpretuję je poprawnie, powinno to oznaczać, że implementacja list, indeksowanie list i dzielenie list w Pythonie jest bardzo wydajna. Jest to wynik zarówno pocieszający, jak i nieoczekiwany.

Czy istnieje inny, „lepszy” sposób przechodzenia po liście w parach?

Zwróć uwagę, że jeśli lista ma nieparzystą liczbę elementów, ostatni nie będzie należeć do żadnej z par.

Jaki byłby właściwy sposób zapewnienia uwzględnienia wszystkich elementów?

Dodałem te dwie sugestie z odpowiedzi do testów:

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

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

Oto wyniki:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Dotychczasowe wyniki

Najbardziej pythonowe i bardzo wydajne:

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

Najbardziej wydajne i bardzo pytoniczne:

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

Zajęło mi chwilę, zanim zrozumiałem, że pierwsza odpowiedź używa dwóch iteratorów, podczas gdy druga używa jednego.

Aby poradzić sobie z sekwencjami z nieparzystą liczbą elementów, zaproponowano rozszerzenie oryginalnej sekwencji o jeden element ( None), który jest łączony z poprzednim ostatnim elementem, co można osiągnąć itertools.izip_longest().

Wreszcie

Zauważ, że w Pythonie 3.x zip()zachowuje się jak itertools.izip()i itertools.izip() zniknął.

Apalala
źródło
RE: „właściwa” droga - nie ma „właściwej” drogi! To zależy od przypadku użycia.
Andrew Jaffe,
@Andrew Jaffe Podałem kryteria „najlepszego” w tym przypadku: wydajne i pytoniczne.
Apalala,
@Apalala: Mam na myśli, że wynik posiadania liczby nieparzystej zależy od zastosowania. Na przykład: możesz po prostu pominąć ostatni element, dodać konkretny znany element fikcyjny lub zduplikować ostatni
Andrew Jaffe
2
@Apalala: ponieważ używasz jakiegoś mumbo-jumbo zamiast timeitmodułu.
SilentGhost

Odpowiedzi:

52

Mój ulubiony sposób na zrobienie tego:

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)

Jeśli chcesz sparować wszystkie elementy, możesz oczywiście potrzebować wartości fill:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)
Jochen Ritzel
źródło
Wydaje się, że w pierwszej (parach) funkcji brakuje klonowania i rozwijania drugiego iteratora. Zobacz itertoolssekcję z przepisami.
Apalala,
@Apalala: zip przesuwa ten sam iterator dwukrotnie.
Jochen Ritzel
Oczywiście masz rację, a parowanie jest jak dotąd najbardziej efektywne, nie wiem dlaczego.
Apalala,
1
Uwielbiam to rozwiązanie: jest leniwe i świetnie wykorzystuje stan iteratorów. Mógłbyś nawet uczynić z tego jedną linijkę, choć być może kosztem czytelności:izip(*[iter(t)]*size)
Channing Moore
jeśli chodzi o drugie rozwiązanie, czy nie chciałbyś uniknąć tworzenia listy, jeśli chodzi o wydajność?
maksymalnie
41

Powiedziałbym, że twoje początkowe rozwiązanie pairs = zip(t[::2], t[1::2])jest najlepsze, ponieważ jest najłatwiejsze do odczytania (aw Pythonie 3 zipautomatycznie zwraca iterator zamiast listy).

Aby zapewnić uwzględnienie wszystkich elementów, możesz po prostu rozszerzyć listę o None.

Następnie, jeśli lista ma nieparzystą liczbę elementów, ostatnia para będzie (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)]
Tim Pietzcker
źródło
6

Zaczynam od małego zastrzeżenia - nie używaj poniższego kodu. To wcale nie jest Pythonic, napisałem dla zabawy. Jest podobny do pairwisefunkcji @ THC4k, ale używa iteri zamyka lambda. Nie używa itertoolsmodułu i nie obsługuje fillvalue. Umieściłem to tutaj, ponieważ ktoś może uznać to za interesujące:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)
Tomasz Elendt
źródło
4

Jeśli chodzi o większość Pythona , powiedziałbym, że przepisy zawarte w dokumentacji źródłowej Pythona (z których niektóre wyglądają podobnie do odpowiedzi, które dostarczył @JochenRitzel) są prawdopodobnie najlepszym rozwiązaniem;)

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)

W nowoczesnym Pythonie wystarczy użyć zip_longest(*args, fillvalue=fillvalue) zgodnie z odpowiednią stroną dokumentacji .

Poklepać
źródło
2

Czy istnieje inny, „lepszy” sposób przechodzenia po liście w parach?

Nie mogę powiedzieć na pewno, ale wątpię: jakiekolwiek inne przejście zawierałoby więcej kodu Pythona, który należy zinterpretować. Wbudowane funkcje, takie jak zip (), są napisane w C, co jest znacznie szybsze.

Jaki byłby właściwy sposób zapewnienia uwzględnienia wszystkich elementów?

Sprawdź długość listy i jeśli jest nieparzysta ( len(list) & 1 == 1), skopiuj listę i dołącz element.

Aaron Digulla
źródło
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)]
Diarmuid O'Briain
źródło
IndexError: pop z pustej listy
HQuser
@HQuser Jasne, otrzymasz ten błąd, jeśli masz nieparzystą liczbę pozycji na liście. Musisz wiedzieć na pewno, że masz pary lub sprawdzić ten błąd.
WaterMolecule
1

Tylko zrób to:

>>> 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)]
Israel Gonçaves de Oliveira
źródło
Twój kod jest odpowiednikiem prostszego list(zip(l, l[1:]))i nie dzieli listy na pary.
Apalala
0

Oto przykład tworzenia par / nóg za pomocą generatora. Generatory są wolne od limitów stosów

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

Przykład:

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

Wynik:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Vlad Bezden
źródło
Porównanie czasu realizacji?
Alan
Lista nie jest podzielona na pary, ponieważ większość liczb na oryginalnej liście występuje w dwóch krotkach. Oczekiwany wynik to[(0, 1), (2, 3), (4, 5)....
Apalala
@Apalala dziękuję za wskazanie.
Poprawiłem
zip()już zwraca generator w Pythonie 3.x, @VladBezden
Apalala
-1

Na wypadek gdyby ktoś potrzebował odpowiedzi pod względem algorytmu, oto jest:

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

Ale pamiętaj, że Twoja oryginalna lista również zostanie zredukowana do ostatniego elementu, ponieważ użyłeś popna niej.

>>> k
[4]
frainmaster
źródło