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.


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)

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

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)

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)

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.


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

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

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]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.