Iterar una lista como par (actual, siguiente) en Python

131

A veces necesito iterar una lista en Python mirando el elemento "actual" y el elemento "siguiente". Hasta ahora lo he hecho con un código como:

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

Esto funciona y hace lo que espero, pero ¿hay alguna forma más idiomática o eficiente de hacer lo mismo?

dcrosta
fuente
Verifique la respuesta de MizardX para esta pregunta . Pero no creo que esta solución sea más idiomática que la tuya.
Fábio Diniz
2
Echa un vistazo a Build a Basic Python Iterator .
mkluwe
39
Como nadie más lo ha mencionado, seré ese tipo y señalaré que usarlo de nextesta manera enmascara un elemento incorporado.
senderle
@senderle Quizás sea Python 2 ...
Quintec
2
@ thecoder16: nexttambién es una función incorporada en Python 2.
zondo

Respuestas:

131

Aquí hay un ejemplo relevante de los documentos del módulo 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)   

Para Python 2, necesita en itertools.iziplugar de 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)

Cómo funciona esto:

Primero, se crean dos iteradores paralelos ay b(la tee()llamada), ambos apuntando al primer elemento del iterable original. El segundo iterador, bse mueve 1 paso adelante (la next(b, None)) llamada). En este punto aapunta a s0 y bapunta a s1. Tanto ay bpuede atravesar el iterador original de forma independiente - la función IZIP toma los dos iteradores y hace que los pares de elementos devueltos, avanzando dos iteradores al mismo ritmo.

Una advertencia: la tee()función produce dos iteradores que pueden avanzar independientemente uno del otro, pero tiene un costo. Si uno de los iteradores avanza más que el otro, entonces tee() necesita mantener los elementos consumidos en la memoria hasta que el segundo iterador los consuma también (no puede 'rebobinar' el iterador original). Aquí no importa porque un iterador está solo un paso por delante del otro, pero en general es fácil usar mucha memoria de esta manera.

Y como tee()puede tomar un nparámetro, esto también se puede usar para más de dos iteradores paralelos:

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
fuente
44
El código de ejemplo es genial ... pero, ¿podría darnos una pequeña explicación de por qué funciona? Como decir qué están haciendo "tee ()" y "next ()" aquí.
John Mulder
@ John Mulder: Hice un breve resumen.
Rafał Dowgird
9
zip(ł, ł[1:])es mucho más corto y pitónico
noɥʇʎԀʎzɐɹƆ
2
@ noɥʇʎԀʎzɐɹƆ: No, no funciona en todos los iterables y hace una copia innecesaria cuando se usa en las listas. Usar funciones es pitónico.
Ry-
Esta función implementada en el funcymódulo: funcy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR
30

¡Ruede el suyo!

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

    for b in it:
        yield (a, b)
        a = b
Ry-
fuente
1
¡Justo lo que necesitaba! ¿Se ha inmortalizado como un método de Python, o tenemos que seguir rodando?
uhoh
1
@uhoh: ¡Hasta ahora no lo sé!
Ry-
21

Como en the_list[1:]realidad crea una copia de toda la lista (excluyendo su primer elemento), y zip()crea una lista de tuplas inmediatamente cuando se llama, en total se crean tres copias de su lista. Si su lista es muy grande, puede preferir

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

que no copia la lista en absoluto.

Sven Marnach
fuente
3
tenga en cuenta que en python 3.x izip se suprime de itertools y debe usar zip incorporado
Xavier Combelle
1
En realidad, no the_list[1:]solo crea un objeto de corte en lugar de una copia de casi toda la lista, por lo que la técnica del OP no es tan derrochadora como la haces sonar.
Martineau
3
Creo que [1:]crea el objeto de división (o posiblemente " 1:"), que se pasa a __slice__la lista, que luego devuelve una copia que contiene solo los elementos seleccionados. Una forma idiomática de copiar una lista es l_copy = l[:](que encuentro feo e ilegible, prefiero l_copy = list(l))
dcrosta el
44
@dcrosta: no hay __slice__un método especial. the_list[1:]es equivalente a the_list[slice(1, None)], que a su vez es equivalente a list.__getitem__(the_list, slice(1, None)).
Sven Marnach
44
@martineau: la copia creada por the_list[1:]es solo una copia superficial, por lo que consta de un solo puntero por elemento de la lista. La parte más intensiva en memoria es la zip()propia, porque creará una lista de una tupleinstancia por elemento de lista, cada una de las cuales contendrá dos punteros a los dos elementos y alguna información adicional. Esta lista consumirá nueve veces la cantidad de memoria que [1:]consume la copia causada por .
Sven Marnach
19

Solo estoy publicando esto, estoy muy sorprendido de que nadie haya pensado en enumerate ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
fuente
11
En realidad, iftambién se puede eliminar si usa rebanado:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance
2
Esta realmente debería ser la respuesta, no depende de ninguna importación adicional y funciona muy bien.
jamescampbell
Sin embargo, no funciona para iterables no indexables, por lo que no es una solución genérica.
wim
14

Iterar por índice puede hacer lo mismo:

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

Salida:

(1, 2)
(2, 3)
(3, 4)
Rumple Stiltskin
fuente
Su respuesta fue más anterior y actual en lugar de actual y siguiente , como en la pregunta. Hice una edición mejorando la semántica para que isiempre sea el índice del elemento actual.
Bengt
1

Esta es ahora una importación simple a partir del 16 de mayo de 2020

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

Documentos para más herramientas de iterto De manera este código es el mismo que en las otras respuestas, pero prefiero las importaciones cuando están disponibles.

Si aún no lo tiene instalado, entonces: pip install more-itertools

Ejemplo

Por ejemplo, si tuviera la secuencia de fibbonnacci, podría calcular las proporciones de pares posteriores como:

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
fuente
0

Pares de una lista usando una comprensión de lista

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)

Salida:

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

Estoy realmente sorprendido de que nadie haya mencionado la solución general más breve, simple y más importante :

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

Funciona para la iteración por pares pasando n=2, pero puede manejar cualquier número mayor:

>>> 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
fuente
-2

Una solución básica:

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