Paare aus einer Liste

97

Oft genug habe ich festgestellt, dass eine Liste paarweise verarbeitet werden muss. Ich habe mich gefragt, welcher pythonische und effiziente Weg dies sein würde, und habe dies bei Google gefunden:

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

Ich dachte, das wäre pythonisch genug, aber nach einer kürzlichen Diskussion zwischen Redewendungen und Effizienz entschied ich mich, einige Tests durchzuführen:

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

Dies waren die Ergebnisse auf meinem Computer:

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

Wenn ich sie richtig interpretiere, sollte dies bedeuten, dass die Implementierung von Listen, Listenindizierung und Listen-Slicing in Python sehr effizient ist. Es ist ein Ergebnis, das sowohl beruhigend als auch unerwartet ist.

Gibt es eine andere, "bessere" Möglichkeit, eine Liste paarweise zu durchlaufen?

Beachten Sie, dass wenn die Liste eine ungerade Anzahl von Elementen enthält, das letzte nicht in einem der Paare enthalten ist.

Welches wäre der richtige Weg, um sicherzustellen, dass alle Elemente enthalten sind?

Ich habe diese beiden Vorschläge aus den Antworten auf die Tests hinzugefügt:

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

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

Dies sind die Ergebnisse:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Ergebnisse bisher

Am pythonischsten und sehr effizientesten:

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

Am effizientesten und sehr pythonisch:

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

Ich brauchte einen Moment, um herauszufinden, dass die erste Antwort zwei Iteratoren verwendet, während die zweite einen einzigen verwendet.

Um Sequenzen mit einer ungeraden Anzahl von Elementen zu behandeln, wurde vorgeschlagen, die ursprüngliche Sequenz um ein Element ( None) zu erweitern, das mit dem vorherigen letzten Element gepaart wird, was erreicht werden kann itertools.izip_longest().

Schließlich

Beachten Sie, dass sich Python 3.x zip()wie folgt verhält itertools.izip()und nicht mehr vorhanden itertools.izip()ist.

Apalala
quelle
RE: der "richtige Weg" - es gibt keinen "richtigen" Weg! Dies hängt vom Anwendungsfall ab.
Andrew Jaffe
@ Andrew Jaffe Ich habe in diesem Fall die Kriterien für "am besten" angegeben: effizient und pythonisch.
Apalala
@Apalala: Ich meine, dass das Ergebnis einer ungeraden Zahl von der Verwendung abhängt. Zum Beispiel: Sie könnten einfach das letzte Element weglassen oder ein bestimmtes bekanntes Dummy-Element hinzufügen oder das letzte duplizieren
Andrew Jaffe
2
@Apalala: weil du anstelle des timeitModuls etwas Hokuspokus verwendest .
SilentGhost

Antworten:

51

Meine Lieblingsmethode:

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)

Wenn Sie alle Elemente koppeln möchten, benötigen Sie möglicherweise einen Füllwert:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)
Jochen Ritzel
quelle
Der ersten (paarweisen) Funktion scheint das Klonen und Vorrücken des zweiten Iterators zu fehlen. Siehe den itertoolsAbschnitt Rezepte.
Apalala
@Apalala: zip rückt denselben Iterator zweimal vor.
Jochen Ritzel
Natürlich haben Sie Recht, und paarweise ist es bisher am effizientesten. Ich weiß nicht warum.
Apalala
1
Ich liebe diese Lösung: Sie ist faul und nutzt die Zustandsfähigkeit von Iteratoren mit großer Wirkung aus. Sie könnten es sogar zu einem Einzeiler machen, allerdings möglicherweise auf Kosten der Lesbarkeit:izip(*[iter(t)]*size)
Channing Moore
Möchten Sie bei Ihrer zweiten Lösung nicht vermeiden, eine Liste zu erstellen, wenn Sie nach der Leistung streben?
Max
40

Ich würde sagen, dass Ihre ursprüngliche Lösung pairs = zip(t[::2], t[1::2])die beste ist, weil sie am einfachsten zu lesen ist (und in Python 3 zipautomatisch einen Iterator anstelle einer Liste zurückgibt).

Um sicherzustellen, dass alle Elemente enthalten sind, können Sie die Liste einfach um erweitern None.

Wenn die Liste dann eine ungerade Anzahl von Elementen enthält, ist das letzte Paar (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
quelle
6

Ich beginne mit einem kleinen Haftungsausschluss - verwenden Sie nicht den folgenden Code. Es ist überhaupt nicht pythonisch, ich habe nur zum Spaß geschrieben. Es ähnelt der @ THC4k- pairwiseFunktion, verwendet iterund schließt sie jedoch lambda. Es verwendet kein itertoolsModul und unterstützt nicht fillvalue. Ich habe es hierher gebracht, weil es jemand interessant finden könnte:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)
Tomasz Elendt
quelle
3

Was die meisten Pythons angeht, würde ich sagen, dass die Rezepte in den Python-Quelldokumenten (von denen einige den Antworten von @JochenRitzel sehr ähnlich sehen) wahrscheinlich die beste Wahl sind;)

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)
Klopfen
quelle
2

Gibt es eine andere, "bessere" Möglichkeit, eine Liste paarweise zu durchlaufen?

Ich kann nicht sicher sagen, aber ich bezweifle es: Jede andere Durchquerung würde mehr Python-Code enthalten, der interpretiert werden muss. Die eingebauten Funktionen wie zip () sind in C geschrieben, was viel schneller ist.

Welches wäre der richtige Weg, um sicherzustellen, dass alle Elemente enthalten sind?

Überprüfen Sie die Länge der Liste und len(list) & 1 == 1kopieren Sie die Liste, wenn sie ungerade ist ( ), und fügen Sie ein Element hinzu.

Aaron Digulla
quelle
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
quelle
IndexError: Pop aus leerer Liste
HQuser
@HQuser Sicher, Sie erhalten diesen Fehler, wenn Sie eine ungerade Anzahl von Elementen in der Liste haben. Sie müssen sicher sein, dass Sie Paare haben, oder auf diesen Fehlerzustand prüfen.
WaterMolecule
0

Mach es nur:

>>> 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
quelle
Ihr Code entspricht dem einfacheren list(zip(l, l[1:]))und teilt die Liste nicht in Paare auf.
Apalala
cool ...........
Israel Gonçaves de Oliveira
0

Hier ist ein Beispiel für die Erstellung von Paaren / Beinen mithilfe eines Generators. Generatoren sind frei von Stapelbeschränkungen

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

Beispiel:

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

Ausgabe:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Vlad Bezden
quelle
Vergleich der Ausführungszeit?
Alan
Die Liste ist nicht paarweise unterteilt, da die meisten Zahlen in der ursprünglichen Liste in zwei Tupeln angezeigt werden. Die erwartete Ausgabe ist[(0, 1), (2, 3), (4, 5)....
Apalala
@ Palala danke für den Hinweis. Ich habe den Code
korrigiert
zip()gibt bereits einen Generator in Python 3.x zurück, @VladBezden
Apalala
-1

Nur für den Fall, dass jemand die Antwort algorithmisch benötigt, hier ist sie:

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

Beachten Sie jedoch, dass Ihre ursprüngliche Liste auch auf das letzte Element reduziert wird, da Sie sie verwendet pophaben.

>>> k
[4]
frainmaster
quelle