Gibt es eine gute pythonische Möglichkeit, eine Liste zu durchlaufen und zwei Elemente neu abzustimmen? Das letzte Element sollte mit dem ersten gepaart werden.
Wenn ich zum Beispiel die Liste [1, 2, 3] habe, möchte ich die folgenden Paare erhalten:
- 1 - 2
- 2 - 3
- 3 - 1
Antworten:
Eine pythonische Möglichkeit, paarweise auf eine Liste zuzugreifen, ist :
zip(L, L[1:])
. So verbinden Sie das letzte Element mit dem ersten:>>> L = [1, 2, 3] >>> zip(L, L[1:] + L[:1]) [(1, 2), (2, 3), (3, 1)]
quelle
itertools
kann eine Lösung verwendet werden.zip
(unter anderem) ist verdammt effizient.zip(*iterables)
ist wie 3list(zip(*iterables))
- weshalb ichizip as zip
in meiner Antwort den Import für Python 2 vorschlage - stackoverflow.com/a/36918720/541136 - JF gehtzip
in dieser Antwort tatsächlich von Python 2 aus , was das Problem der unnötigen Erstellung langer Listen verschärft in Erinnerung. Die Antwort von JF verwendet 200% mehr Speicher als für Listen erforderlich (8 Bytes pro Element) sowie den Speicher für jedes Tupel (relativ enorm mit 64 Bytes pro Tupel, siehe stackoverflow.com/a/30316760/541136 ). 800% mehr. also 1000% mehr insgesamt (unter der Annahme einer faulen Bewertung)Ich würde ein
deque
mit verwendenzip
, um dies zu erreichen.>>> from collections import deque >>> >>> l = [1,2,3] >>> d = deque(l) >>> d.rotate(-1) >>> zip(l, d) [(1, 2), (2, 3), (3, 1)]
quelle
deque.rotate
was äußerst nützlich sein kann.Ich würde eine geringfügige Änderung des
pairwise
Rezepts aus deritertools
Dokumentation verwenden :def pairwise_circle(iterable): "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)" a, b = itertools.tee(iterable) first_value = next(b, None) return itertools.zip_longest(a, b,fillvalue=first_value)
Dadurch wird einfach ein Verweis auf den ersten Wert beibehalten, und wenn der zweite Iterator erschöpft ist,
zip_longest
wird die letzte Stelle mit dem ersten Wert gefüllt.(Beachten Sie auch, dass es sowohl mit Iteratoren wie Generatoren als auch mit Iterablen wie Listen / Tupeln funktioniert.)
Beachten Sie, dass die Lösung von @ Barry dieser sehr ähnlich ist, aber meiner Meinung nach etwas leichter zu verstehen und über ein Element hinaus zu erweitern ist.
quelle
cycle
.itertools.zip_longest(a, b, fillvalue=first_value)
dann offensichtlicherizip(a, chain(b, (first,)))
- keine Notwendigkeit, zusätzliches Tupel zu machen(first, )
Ich würde mich paaren
itertools.cycle
mitzip
:import itertools def circular_pairwise(l): second = itertools.cycle(l) next(second) return zip(l, second)
cycle
Gibt eine Iterable zurück, die die Werte ihres Arguments der Reihe nach liefert und vom letzten zum ersten Wert durchläuft.Wir überspringen den ersten Wert, also beginnt er an der Position
1
(anstatt0
).Als nächstes machen wir
zip
es mit der ursprünglichen, nicht mutierten Liste.zip
ist gut, weil es aufhört, wenn eines seiner iterierbaren Argumente erschöpft ist.Auf diese Weise wird die Erstellung von Zwischenlisten vermieden:
cycle
Enthält einen Verweis auf das Original, kopiert ihn jedoch nicht.zip
arbeitet auf die gleiche Weise.Es ist wichtig zu beachten, dass dies unterbrochen wird, wenn die Eingabe eine ist
iterator
, z. B. afile
, (oder amap
oderzip
inPython-3), da das Vorrücken an einer Stelle (durchnext(second)
) den Iterator automatisch an allen anderen vorrücken wird. Dies lässt sich leicht lösen, indemitertools.tee
zwei unabhängig voneinander arbeitende Iteratoren über die ursprüngliche Iteration erzeugt werden:def circular_pairwise(it): first, snd = itertools.tee(it) second = itertools.cycle(snd) next(second) return zip(first, second)
tee
kann große Mengen zusätzlichen Speichers verwenden, wenn beispielsweise einer der zurückgegebenen Iteratoren aufgebraucht ist, bevor der andere berührt wird. Da wir jedoch immer nur einen Schritt Unterschied haben, ist der zusätzliche Speicher minimal.quelle
itertools
funktionalen Programmierung im Allgemeinen liebe. Sie ermöglicht extrem sauberen, präzisen Code und ist dennoch leicht zu lesen.cycle
Dokumenten wird ausdrücklich angegeben, dass sie eine Kopie jedes Elements in der iterierbaren Eingabe speichern. Sie sparen also keinen Platz beim Erstellen einer Kopie der Liste. Einige der anderen Antworten vermeiden dieses Problem mitchain
oderfillvalue
.Es gibt effizientere Methoden (die keine temporären Listen erstellen), aber ich denke, dies ist die prägnanteste:
> l = [1,2,3] > zip(l, (l+l)[1:]) [(1, 2), (2, 3), (3, 1)]
quelle
(l+l)
doppelt so groß ist.Ich würde ein Listenverständnis verwenden und die Tatsache ausnutzen, dass dies
l[-1]
das letzte Element ist.>>> l = [1,2,3] >>> [(l[i-1],l[i]) for i in range(len(l))] [(3, 1), (1, 2), (2, 3)]
Auf diese Weise benötigen Sie keine temporäre Liste.
quelle
xrange
in Python 2 benötigen , um eine temporäre Liste zu vermeiden. Das Erstellen einer Liste mit Zahlen von 1 bis n ist genauso teuer wie das Erstellen einer flachen Kopie einer Liste mitn
Elementen.[(l[i-1], it) for i, it in enumerate(l)]
. Sie sollten wahrscheinlich beachten, dass bei beiden Methoden das zuletzt angeforderte Element an erster Stelle steht.Wenn Ihnen die akzeptierte Antwort gefällt,
zip(L, L[1:] + L[:1])
Sie können mit semantisch demselben Code viel mehr Speicherlicht erzeugen, indem Sie Folgendes verwenden
itertools
:from itertools import islice, chain #, izip as zip # uncomment if Python 2
Und dies materialisiert kaum etwas im Speicher, das über die ursprüngliche Liste hinausgeht (vorausgesetzt, die Liste ist relativ groß):
zip(l, chain(islice(l, 1, None), islice(l, None, 1)))
Zum Verwenden verbrauchen Sie einfach (zum Beispiel mit einer Liste):
>>> list(zip(l, chain(islice(l, 1, None), islice(l, None, 1)))) [(1, 2), (2, 3), (3, 1)]
Dies kann auf jede Breite erweiterbar gemacht werden:
def cyclical_window(l, width=2): return zip(*[chain(islice(l, i, None), islice(l, None, i)) for i in range(width)])
und Verwendung:
>>> l = [1, 2, 3, 4, 5] >>> cyclical_window(l) <itertools.izip object at 0x112E7D28> >>> list(cyclical_window(l)) [(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)] >>> list(cyclical_window(l, 4)) [(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 1), (4, 5, 1, 2), (5, 1, 2, 3)]
Unbegrenzte Generierung mit
itertools.tee
mitcycle
Sie können auch verwenden
tee
, um zu vermeiden, dass ein redundantes Zyklusobjekt erstellt wird:from itertools import cycle, tee ic1, ic2 = tee(cycle(l)) next(ic2) # must still queue up the next item
und nun:
>>> [(next(ic1), next(ic2)) for _ in range(10)] [(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]
Das ist unglaublich effizient, eine erwartete Nutzung
iter
mitnext
und eleganter Verwendung voncycle
,tee
undzip
.Gehen Sie nicht
cycle
direkt zu weiter, eslist
sei denn, Sie haben Ihre Arbeit gespeichert und haben Zeit, damit Ihr Computer zum Stillstand kommt, wenn Sie den Speicher voll ausschöpfen. Wenn Sie Glück haben, wird Ihr Betriebssystem den Prozess nach einer Weile abbrechen, bevor er Ihren Computer zum Absturz bringt .Integrierte Python-Funktionen
Schließlich keine Standard-Lib-Importe, aber dies funktioniert nur bis zur Länge der ursprünglichen Liste (sonst IndexError.)
>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))] [(1, 2), (2, 3), (3, 1)]
Sie können dies mit modulo fortsetzen:
>>> len_l = len(l) >>> [(l[i % len_l], l[(i + 1) % len_l]) for i in range(10)] [(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]
quelle
range(10)
der Zyklus niemals enden wird , wenn Sie (in diesem Fall ) keinen Endpunkt haben.Erstaunlich, wie viele verschiedene Möglichkeiten es gibt, dieses Problem zu lösen.
Hier ist noch einer. Sie können das
pairwise
Rezept verwenden, aber anstatt es zu komprimierenb
,chain
mit dem ersten Element, das Sie bereits entfernt haben. Muss nicht,cycle
wenn wir nur einen einzigen zusätzlichen Wert benötigen:from itertools import chain, izip, tee def pairwise_circle(iterable): a, b = tee(iterable) first = next(b, None) return izip(a, chain(b, (first,)))
quelle
Ich mag eine Lösung, die die ursprüngliche Liste nicht ändert und die Liste nicht in den temporären Speicher kopiert:
def circular(a_list): for index in range(len(a_list) - 1): yield a_list[index], a_list[index + 1] yield a_list[-1], a_list[0] for x in circular([1, 2, 3]): print x
Ausgabe:
(1, 2) (2, 3) (3, 1)
Ich kann mir vorstellen, dass dies für einige sehr große In-Memory-Daten verwendet wird.
quelle
Dieser funktioniert auch dann, wenn die Liste den
l
größten Teil des Systemspeichers belegt hat. (Wenn etwas garantiert, dass dieser Fall unmöglich ist, ist der von chepner gepostete Reißverschluss in Ordnung.)l.append( l[0] ) for i in range( len(l)-1): pair = l[i],l[i+1] # stuff involving pair del l[-1]
oder allgemeiner (funktioniert für jeden Offset
n
dhl[ (i+n)%len(l) ]
)for i in range( len(l)): pair = l[i], l[ (i+1)%len(l) ] # stuff
vorausgesetzt, Sie befinden sich auf einem System mit anständig schneller Modulo-Teilung (dh nicht mit einem eingebetteten System mit Erbsenhirn).
Es scheint eine weit verbreitete Überzeugung zu geben, dass die Indizierung einer Liste mit einem ganzzahligen Index nicht pythonisch ist und am besten vermieden wird. Warum?
quelle
for i in range(len(a_list)): print a_list[i]
stattfor x in a_list: print x
.for el in iterable:
ist dies der richtige Weg. Wenn Sie auch den Index benötigen,enumerate()
ist der richtige Weg. Eine manuelle Indizierung sollte daher vermieden werden.Dies ist meine Lösung, und sie sieht für mich pythonisch genug aus:
l = [1,2,3] for n,v in enumerate(l): try: print(v,l[n+1]) except IndexError: print(v,l[0])
Drucke:
1 2 2 3 3 1
Die Generatorfunktionsversion:
def f(iterable): for n,v in enumerate(iterable): try: yield(v,iterable[n+1]) except IndexError: yield(v,iterable[0]) >>> list(f([1,2,3])) [(1, 2), (2, 3), (3, 1)]
quelle
Wie wäre es damit?
li = li+[li[0]] pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]
quelle
from itertools import izip, chain, islice itr = izip(l, chain(islice(l, 1, None), islice(l, 1)))
(Wie oben mit @ jf-sebastians "zip" -Antwort, jedoch mit itertools.)
NB: BEARBEITET mit hilfreichem Anstoß von @ 200_success . zuvor war:
itr = izip(l, chain(l[1:], l[:1]))
quelle
itertools
möchten, ist die Lösung von @ RoadieRich besser, da das Kopieren und Schneiden vermieden wird.Nur ein weiterer Versuch
>>> L = [1,2,3] >>> zip(L,L[1:]) + [(L[-1],L[0])] [(1, 2), (2, 3), (3, 1)]
quelle
Wenn Sie nicht zu viel Speicher verbrauchen möchten, können Sie meine Lösung ausprobieren:
[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]
Es ist etwas langsamer, verbraucht aber weniger Speicher.
quelle
L = [1, 2, 3] a = Zip (L, L [1:] + L [: 1]) für i in a: b = Liste (i) drucke b
quelle
In der kommenden Zeit
Python 3.10
release schedule
bietet die neuepairwise
Funktion eine Möglichkeit, gleitende Paare aufeinanderfolgender Elemente zu erstellen:from itertools import pairwise # l = [1, 2, 3] list(pairwise(l + l[:1])) # [(1, 2), (2, 3), (3, 1)]
oder einfach,
pairwise(l + l[:1])
wenn Sie das Ergebnis nicht alslist
.Beachten Sie, dass wir
pairwise
in der Liste mit dem Kopf (l + l[:1]
) angehängt sind, sodass rollende Paare kreisförmig sind (dh, dass wir auch das(3, 1)
Paar einschließen ):list(pairwise(l)) # [(1, 2), (2, 3)] l + l[:1] # [1, 2, 3, 1]
quelle
Dies scheint, als würden Kombinationen den Job machen.
from itertools import combinations x=combinations([1,2,3],2)
Dies würde einen Generator ergeben. Dies kann dann als solches wiederholt werden
for i in x: print i
Die Ergebnisse würden ungefähr so aussehen
(1, 2) (1, 3) (2, 3)
quelle
[1,2,3,4,5]
das Ergebnis[(1,2), (2,3), (3,4), (4,5), (5,1)]
nicht Kombinationen, sondern nur Paare in der Liste sind.