Iterate un elenco come coppia (attuale, successivo) in Python


130

A volte ho bisogno di iterare un elenco in Python guardando l'elemento "corrente" e l'elemento "successivo". Finora l'ho fatto con un codice come:

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

Funziona e fa quello che mi aspetto, ma c'è un modo più idiomatico o efficiente per fare la stessa cosa?


Controlla la risposta di MizardX per questa domanda . Ma non penso che questa soluzione sia più idiomatica della tua.
Fábio Diniz,

2
Dai un'occhiata a Build a Basic Python Iterator .
mkluwe,

39
dato che nessun altro lo ha menzionato, sarò quel ragazzo e sottolineo che l'uso in nextquesto modo maschera un built-in.
mittente

@senderle Forse è Python 2 ...
Quintec,

2
@ thecoder16: nextè anche una funzione integrata in Python 2.
zondo

Risposte:


131

Ecco un esempio pertinente dai documenti del modulo 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)   

Per Python 2, itertools.izipinvece di 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)

Come funziona:

Innanzitutto, vengono creati due iteratori paralleli ae b(la tee()chiamata), entrambi puntano al primo elemento dell'iterabile originale. Il secondo iteratore bviene spostato di un passo avanti (la next(b, None)) chiamata). A questo punto apunta a s0 e bpunta a s1. Entrambi ae bpossono attraversare l'iteratore originale in modo indipendente: la funzione izip prende i due iteratori e crea coppie di elementi restituiti, facendo avanzare entrambi gli iteratori allo stesso ritmo.

Un avvertimento: la tee()funzione produce due iteratori che possono avanzare indipendentemente l'uno dall'altro, ma ha un costo. Se uno degli iteratori avanza ulteriormente rispetto agli altri, è tee() necessario mantenere in memoria gli elementi consumati fino a quando il secondo iteratore non li assuma (non può "riavvolgere" l'iteratore originale). Qui non importa perché un iteratore è solo 1 passo avanti rispetto all'altro, ma in generale è facile usare molta memoria in questo modo.

E poiché tee()può assumere un nparametro, questo può essere utilizzato anche per più di due iteratori paralleli:

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)

4
Il codice di esempio è fantastico ... ma potresti dare un po 'di spiegazione sul perché funziona? Ad esempio, dì cosa stanno facendo "tee ()" e "next ()".
John Mulder,

@ John Mulder: ha fatto un breve riassunto.
Rafał Dowgird,

9
zip(ł, ł[1:])è molto più breve e pitonico
noɥʇʎԀʎzɐɹƆ

2
@ noɥʇʎԀʎzɐɹƆ: No, non funziona su tutti gli iterabili e crea una copia non necessaria quando viene utilizzato nelle liste. L'uso delle funzioni è pitonico.
Ry-

Questa funzione implementata nel funcymodulo: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR

30

Crea il tuo!

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

    for b in it:
        yield (a, b)
        a = b

1
Proprio quello di cui avevo bisogno! È stato immortalato come metodo Python o dobbiamo continuare a girare?
UHOH

1
@uhoh: non ho ancora visto quello che so!
Ry-

21

Poiché in the_list[1:]realtà crea una copia dell'intero elenco (escluso il suo primo elemento) e zip()crea immediatamente un elenco di tuple quando viene chiamato, vengono create in totale tre copie dell'elenco. Se la tua lista è molto grande, potresti preferire

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

che non copia affatto l'elenco.


3
nota che in Python 3.x izip è soppresso da itertools e dovresti usare zip integrata
Xavier Combelle

1
In realtà, non si tratta the_list[1:]solo di creare un oggetto fetta piuttosto che una copia di quasi l'intero elenco, quindi la tecnica dell'OP non è così dispendiosa come sembra.
martineau,

3
Penso che [1:]crei l'oggetto slice (o eventualmente " 1:"), che viene passato __slice__nell'elenco, che quindi restituisce una copia contenente solo gli elementi selezionati. Un modo idiomatico per copiare un elenco è l_copy = l[:](che trovo brutto e illeggibile - preferisco l_copy = list(l))
dcrosta

4
@dcrosta: non esiste __slice__un metodo speciale. the_list[1:]è equivalente a the_list[slice(1, None)], che a sua volta è equivalente a list.__getitem__(the_list, slice(1, None)).
Sven Marnach,

4
@martineau: la copia creata da the_list[1:]è solo una copia superficiale, quindi è costituita da un solo puntatore per elemento dell'elenco. La parte più intensiva della memoria è la zip()stessa, poiché creerà un elenco di tupleun'istanza per elemento dell'elenco, ognuno dei quali conterrà due puntatori ai due elementi e alcune informazioni aggiuntive. Questo elenco consumerà nove volte la quantità di memoria che la copia causata dai [1:]consuma.
Sven Marnach,

19

Lo sto solo dicendo, sono molto sorpreso che nessuno abbia pensato di enumerare ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something

11
In realtà, ifpuò anche essere rimosso se si utilizza l'affettamento:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance

2
Questa dovrebbe davvero essere la risposta, non si basa su importazioni extra e funziona alla grande.
Jamescampbell,

Tuttavia, non funziona per iterabili non indicizzabili, quindi non è una soluzione generica.
mercoledì

14

L'iterazione per indice può fare la stessa cosa:

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

Produzione:

(1, 2)
(2, 3)
(3, 4)

La tua risposta è stata più precedente e attuale anziché attuale e successiva , come nella domanda. Ho fatto una modifica migliorando la semantica in modo che isia sempre l'indice dell'elemento corrente.
Bengt,

1

Questo è ora un semplice Import A partire dal 16 maggio 2020

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

Documenti per più-itertools Questo codice è lo stesso delle altre risposte, ma preferisco di gran lunga le importazioni quando disponibili.

Se non lo hai già installato, allora: pip install more-itertools

Esempio

Ad esempio, se avessi la sequenza fibbonnacci, potresti calcolare i rapporti delle coppie successive come:

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

0

Associa un elenco usando una comprensione dell'elenco

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)

Produzione:

(1, 2)
(2, 3)
(3, 4)

0

Sono davvero sorpreso che nessuno abbia menzionato la soluzione più breve, più semplice e, soprattutto, generale :

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

Funziona per l'iterazione a coppie passando n=2, ma può gestire qualsiasi numero superiore:

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

-2

Una soluzione di base:

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 )

-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)]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.