Iteruj listę jako parę (bieżącą, następną) w Pythonie

139

Czasami muszę powtórzyć listę w Pythonie, patrząc na „bieżący” element i „następny” element. Do tej pory robiłem to z kodem takim:

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

To działa i robi to, czego oczekuję, ale czy istnieje bardziej idiomatyczny lub skuteczny sposób na zrobienie tego samego?

dcrosta
źródło
Sprawdź odpowiedź MizardX na to pytanie . Ale nie sądzę, żeby to rozwiązanie było bardziej idiomatyczne niż twoje.
Fábio Diniz,
42
ponieważ nikt inny o tym nie wspomniał, będę tym facetem i wskażę, że użycie nexttego sposobu maskuje wbudowaną.
nadawca
@senderle Może to Python 2…
Quintec
3
@ thecoder16: nextjest również funkcją wbudowaną w Pythonie 2.
zondo

Odpowiedzi:

135

Oto odpowiedni przykład z dokumentacji modułu itertools :

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

W przypadku Pythona 2 itertools.izipzamiast 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)

Jak to działa:

Najpierw tworzone są dwa równoległe iteratory ai b( tee()wywołanie), oba wskazujące na pierwszy element oryginalnego iterowalnego. Drugi iterator bjest przesuwany o 1 krok do przodu ( next(b, None)wywołanie). W tym momencie awskazuje na s0 i bwskazuje na s1. Oba ai bmogą niezależnie przechodzić przez oryginalny iterator - funkcja izip przyjmuje dwa iteratory i tworzy pary zwracanych elementów, przesuwając oba iteratory w tym samym tempie.

Jedno zastrzeżenie: tee()funkcja generuje dwa iteratory, które mogą się rozwijać niezależnie od siebie, ale wiąże się to z pewnym kosztem. Jeśli jeden z iteratorów posuwa się dalej niż drugi, to tee() musi przechowywać zużyte elementy w pamięci, dopóki drugi iterator również ich nie pochłonie (nie może „przewinąć” oryginalnego iteratora). Tutaj nie ma to znaczenia, ponieważ jeden iterator jest tylko o krok przed drugim, ale ogólnie w ten sposób można łatwo wykorzystać dużo pamięci.

A ponieważ tee()może przyjmować nparametr, można go również użyć dla więcej niż dwóch równoległych iteratorów:

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
źródło
4
Przykładowy kod jest świetny ... ale czy możesz wyjaśnić, dlaczego to działa? Powiedz na przykład, co tu robią „tee ()” i „next ()”.
John Mulder,
@John Mulder: Zrobiłem krótkie podsumowanie.
Rafał Dowgird
9
zip(ł, ł[1:])jest znacznie krótszy i
pytoniczny
2
@ noɥʇʎԀʎzɐɹƆ: Nie, nie działa na każdej iteracji i tworzy niepotrzebną kopię, gdy jest używany na listach. Używanie funkcji jest pythonowe.
Ry-
Ta funkcja zaimplementowana w funcymodule: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR
35

Skręć swój własny!

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

    for b in it:
        yield (a, b)
        a = b
Ry-
źródło
2
Dokładnie to, czego potrzebowałem! Czy zostało to uwiecznione jako metoda Pythona, czy też musimy kontynuować?
uhoh
1
@uhoh: O ile wiem, jeszcze nie ma!
Ry-
Dziwię się, że to nie jest akceptowana odpowiedź. Brak importu i logika stojąca za tym jest bardzo łatwa do zrozumienia. +1 zdecydowanie.
devianceee
21

Ponieważ the_list[1:]faktycznie tworzy kopię całej listy (z wyłączeniem jej pierwszego elementu) i zip()tworzy listę krotek natychmiast po wywołaniu, w sumie tworzone są trzy kopie listy. Jeśli Twoja lista jest bardzo duża, możesz preferować

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

który w ogóle nie kopiuje listy.

Sven Marnach
źródło
3
zauważ, że w pythonie 3.x izip jest pomijany w itertools i powinieneś używać wbudowanego zip
Xavier Combelle
1
W rzeczywistości nie the_list[1:]tworzy się po prostu obiektu wycinka, a nie kopii prawie całej listy - więc technika OP nie jest tak marnotrawna, jak wydaje się to brzmieć.
martineau
3
Myślę, że [1:]tworzy obiekt plasterka (lub prawdopodobnie „ 1:”), który jest przekazywany __slice__na listę, a następnie zwraca kopię zawierającą tylko wybrane elementy. Jednym idiomatycznym sposobem skopiowania listy jest l_copy = l[:](co uważam za brzydkie i nieczytelne - wolę l_copy = list(l))
dcrosta
4
@dcrosta: Nie ma __slice__specjalnej metody. the_list[1:]jest równoważne the_list[slice(1, None)], co z kolei jest równoważne list.__getitem__(the_list, slice(1, None)).
Sven Marnach,
4
@martineau: kopia utworzona przez the_list[1:]jest tylko płytką kopią, więc zawiera tylko jeden wskaźnik na element listy. Część bardziej wymagająca pamięci jest zip()sama w sobie, ponieważ utworzy listę jednej tupleinstancji na element listy, z których każda będzie zawierać dwa wskaźniki do dwóch pozycji i dodatkowe informacje. Ta lista zajmie dziewięć razy więcej pamięci niż kopia spowodowana przez [1:]użycie.
Sven Marnach,
21

Właśnie to publikuję, jestem bardzo zaskoczony, że nikt nie pomyślał o enumerate ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
źródło
12
Właściwie ifmożna również usunąć, jeśli używasz krojenia:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
saldo ratunkowe
2
To naprawdę powinna być odpowiedź, nie polega na żadnym dodatkowym imporcie i działa świetnie.
jamescampbell
1
Jednak nie działa w przypadku nieindeksowalnych elementów iteracyjnych, więc nie jest to rozwiązanie ogólne.
wim
15

Iterowanie według indeksu może zrobić to samo:

#!/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)

Wynik:

(1, 2)
(2, 3)
(3, 4)
Rumple Stiltskin
źródło
Twoja odpowiedź była bardziej poprzednia i aktualna zamiast aktualnej i następnej , jak w pytaniu. Zrobiłem edycję poprawiającą semantykę tak, aby izawsze był to indeks aktualnego elementu.
Bengt
2

To jest teraz prosty import z 16 maja 2020 r

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

Dokumenty dla more-itertools Pod maską ten kod jest taki sam jak w innych odpowiedziach, ale zdecydowanie wolę importować, gdy jest dostępny.

Jeśli jeszcze go nie masz, to: pip install more-itertools

Przykład

Na przykład, gdybyś miał sekwencję Fibbonnacciego, mógłbyś obliczyć stosunki kolejnych par jako:

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
źródło
0

Pary z listy przy użyciu rozumienia listy

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)

Wynik:

(1, 2)
(2, 3)
(3, 4)
Bengt
źródło
0

Jestem naprawdę zaskoczony, że nikt nie wspomniał o krótszej, prostszej i co najważniejsze ogólnej rozwiązaniu:

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

Działa dla iteracji parami przez przekazanie n=2, ale może obsłużyć każdą większą liczbę:

>>> 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
źródło
-1

Podstawowe rozwiązanie:

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
źródło
-1
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
źródło