Iterieren Sie eine Liste als Paar (aktuell, weiter) in Python

131

Manchmal muss ich eine Liste in Python iterieren und dabei das "aktuelle" Element und das "nächste" Element betrachten. Ich habe dies bis jetzt mit Code wie: getan.

for current, next in zip(the_list, the_list[1:]):
    # Do something

Dies funktioniert und macht das, was ich erwarte, aber gibt es eine idiomatischere oder effizientere Möglichkeit, dasselbe zu tun?

dcrosta
quelle
Überprüfen Sie die MizardX-Antwort auf diese Frage . Aber ich denke nicht, dass diese Lösung idiomatischer ist als Ihre.
Fábio Diniz
2
Schauen Sie sich Build a Basic Python Iterator an .
mkluwe
39
da es sonst niemand erwähnt hat, werde ich dieser Typ sein und darauf hinweisen, dass auf nextdiese Weise ein eingebautes maskiert wird.
senderle
@senderle Vielleicht ist es Python 2…
Quintec
2
@ thecoder16: nextist auch eine in Python 2
integrierte

Antworten:

131

Hier ist ein relevantes Beispiel aus den Dokumenten des itertools- Moduls:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)   

Für Python 2 benötigen Sie itertools.izipanstelle von zip:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

So funktioniert das:

Zuerst werden zwei parallel Iteratoren, aund bwerden erstellt (der tee()Anruf), die beide nach dem ersten Element des ursprünglichen iterable. Der zweite Iterator bwird 1 Schritt vorwärts (der next(b, None)Aufruf) verschoben . An dieser Stelle azeigt auf s0 und bzeigt auf s1. Beide aund bkönnen den ursprünglichen Iterator unabhängig voneinander durchlaufen - die izip-Funktion verwendet die beiden Iteratoren und erstellt Paare der zurückgegebenen Elemente, wobei beide Iteratoren im gleichen Tempo weiterentwickelt werden.

Eine Einschränkung: Die tee()Funktion erzeugt zwei Iteratoren, die unabhängig voneinander vorrücken können, jedoch mit Kosten verbunden sind. Wenn einer der Iteratoren weiter voranschreitet als der andere, tee() müssen die verbrauchten Elemente im Speicher bleiben, bis der zweite Iterator sie ebenfalls verbraucht (der ursprüngliche Iterator kann nicht zurückgespult werden). Hier spielt es keine Rolle, da ein Iterator dem anderen nur einen Schritt voraus ist, aber im Allgemeinen ist es einfach, auf diese Weise viel Speicher zu verwenden.

Und da tee()ein nParameter verwendet werden kann, kann dieser auch für mehr als zwei parallele Iteratoren verwendet werden:

def threes(iterator):
    "s -> (s0,s1,s2), (s1,s2,s3), (s2, s3,4), ..."
    a, b, c = itertools.tee(iterator, 3)
    next(b, None)
    next(c, None)
    next(c, None)
    return zip(a, b, c)
Rafał Dowgird
quelle
4
Der Beispielcode ist großartig ... aber können Sie ein wenig erklären, warum er funktioniert? Sagen Sie gerne, was "tee ()" und "next ()" hier machen.
John Mulder
@ John Mulder: Habe eine kurze Zusammenfassung gemacht.
Rafał Dowgird
9
zip(ł, ł[1:])ist viel kürzer und pythonisch
noɥʇʎԀʎzɐɹƆ
2
@ noɥʇʎԀʎzɐɹƆ: Nein, es funktioniert nicht bei jedem Iterable und erstellt eine unnötige Kopie, wenn es in Listen verwendet wird. Die Verwendung von Funktionen ist pythonisch.
Ry-
Diese Funktion ist in realisiert funcyModul: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR
30

Roll deinen eigenen!

def pairwise(iterable):
    it = iter(iterable)
    a = next(it, None)

    for b in it:
        yield (a, b)
        a = b
Ry-
quelle
1
Genau das, was ich brauchte! Wurde dies als Python-Methode verewigt oder müssen wir weitermachen?
Uhoh
1
@uhoh: Soweit ich weiß noch nicht!
Ry-
21

Da the_list[1:]tatsächlich eine Kopie der gesamten Liste (mit Ausnahme des ersten Elements) erstellt wird und zip()beim Aufruf sofort eine Liste der Tupel erstellt wird, werden insgesamt drei Kopien Ihrer Liste erstellt. Wenn Ihre Liste sehr groß ist, bevorzugen Sie vielleicht

from itertools import izip, islice
for current_item, next_item in izip(the_list, islice(the_list, 1, None)):
    print(current_item, next_item)

das kopiert die Liste überhaupt nicht.

Sven Marnach
quelle
3
Beachten Sie, dass in Python 3.x izip von itertools unterdrückt wird und Sie die integrierte Zip-Datei verwenden sollten
Xavier Combelle
1
Tatsächlich wird nicht the_list[1:]nur ein Slice-Objekt erstellt, sondern eine Kopie fast der gesamten Liste. Die Technik des OP ist also nicht ganz so verschwenderisch, wie Sie es klingen lassen.
Martineau
3
Ich denke, [1:]erstellt das Slice-Objekt (oder möglicherweise " 1:"), das in __slice__der Liste übergeben wird, und gibt dann eine Kopie zurück, die nur die ausgewählten Elemente enthält. Eine idiomatische Art, eine Liste zu kopieren, ist l_copy = l[:](was ich hässlich und unlesbar finde - lieber l_copy = list(l))
dcrosta
4
@dcrosta: Es gibt keine __slice__spezielle Methode. the_list[1:]ist äquivalent zu the_list[slice(1, None)], was wiederum äquivalent zu ist list.__getitem__(the_list, slice(1, None)).
Sven Marnach
4
@martineau: Die von erstellte Kopie the_list[1:]ist nur eine flache Kopie, daher besteht sie nur aus einem Zeiger pro Listenelement. Der speicherintensivere Teil ist der zip()selbst, da eine Liste mit einer tupleInstanz pro Listenelement erstellt wird, die jeweils zwei Zeiger auf die beiden Elemente und einige zusätzliche Informationen enthält. Diese Liste belegt das Neunfache des Speichers, den die Kopie [1:]verbraucht.
Sven Marnach
19

Ich lösche das nur, ich bin sehr überrascht, dass niemand daran gedacht hat, aufzuzählen ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
quelle
11
Tatsächlich ifkann das auch entfernt werden, wenn Sie das Schneiden verwenden:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
Lebensbalance
2
Dies sollte wirklich die Antwort sein, es ist nicht auf zusätzliche Importe angewiesen und funktioniert hervorragend.
Jamescampbell
Es funktioniert jedoch nicht für nicht indizierbare Iterables, sodass es keine generische Lösung ist.
wim
14

Das Iterieren nach Index kann dasselbe bewirken:

#!/usr/bin/python
the_list = [1, 2, 3, 4]
for i in xrange(len(the_list) - 1):
    current_item, next_item = the_list[i], the_list[i + 1]
    print(current_item, next_item)

Ausgabe:

(1, 2)
(2, 3)
(3, 4)
Rumple Stiltskin
quelle
Ihre Antwort war eher vorher und aktuell als aktuell und als nächstes , wie in der Frage. Ich habe eine Bearbeitung vorgenommen, um die Semantik so zu verbessern, dass iimmer der Index des aktuellen Elements angezeigt wird.
Bengt
1

Dies ist jetzt ein einfacher Import ab dem 16. Mai 2020

from more_itertools import pairwise
for current, next in pairwise(your_iterable):
  print(f'Current = {current}, next = {nxt}')

Dokumente für mehr itertools Unter der Haube ist dieser Code der gleiche wie in den anderen Antworten, aber ich bevorzuge Importe, wenn verfügbar.

Wenn Sie es noch nicht installiert haben, dann: pip install more-itertools

Beispiel

Wenn Sie beispielsweise die Fibbonnacci-Sequenz hätten, könnten Sie die Verhältnisse nachfolgender Paare wie folgt berechnen:

from more_itertools import pairwise
fib= [1,1,2,3,5,8,13]
for current, nxt in pairwise(fib):
    ratio=current/nxt
    print(f'Curent = {current}, next = {nxt}, ratio = {ratio} ')
jabberwocky
quelle
0

Paare aus einer Liste unter Verwendung eines Listenverständnisses

the_list = [1, 2, 3, 4]
pairs = [[the_list[i], the_list[i + 1]] for i in range(len(the_list) - 1)]
for [current_item, next_item] in pairs:
    print(current_item, next_item)

Ausgabe:

(1, 2)
(2, 3)
(3, 4)
Bengt
quelle
0

Ich bin wirklich überrascht, dass niemand die kürzere, einfachere und vor allem allgemeine Lösung erwähnt hat:

Python 3:

from itertools import islice

def n_wise(iterable, n):
    return zip(*(islice(iterable, i, None) for i in range(n)))

Python 2:

from itertools import izip, islice

def n_wise(iterable, n):
    return izip(*(islice(iterable, i, None) for i in xrange(n)))

Es funktioniert für die paarweise Iteration durch Übergeben n=2, kann jedoch jede höhere Zahl verarbeiten:

>>> for a, b in n_wise('Hello!', 2):
>>>     print(a, b)
H e
e l
l l
l o
o !

>>> for a, b, c, d in n_wise('Hello World!', 4):
>>>     print(a, b, c, d)
H e l l
e l l o
l l o
l o   W
o   W o
  W o r
W o r l
o r l d
r l d !
Marco Bonelli
quelle
-2

Eine grundlegende Lösung:

def neighbors( list ):
  i = 0
  while i + 1 < len( list ):
    yield ( list[ i ], list[ i + 1 ] )
    i += 1

for ( x, y ) in neighbors( list ):
  print( x, y )
mkluwe
quelle
-2
code = '0016364ee0942aa7cc04a8189ef3'
# Getting the current and next item
print  [code[idx]+code[idx+1] for idx in range(len(code)-1)]
# Getting the pair
print  [code[idx*2]+code[idx*2+1] for idx in range(len(code)/2)]
Russell Wong
quelle