2008-10-28 8 views
18

Tengo dos iteradores, un list y un objeto itertools.count (es decir, un generador de valor infinito). Me gustaría fusionar estos dos en un iterador como resultado que se alternarán los valores de rendimiento entre los dos:¿Cómo fusiono dos iteradores de python?

>>> import itertools 
>>> c = itertools.count(1) 
>>> items = ['foo', 'bar'] 
>>> merged = imerge(items, c) # the mythical "imerge" 
>>> merged.next() 
'foo' 
>>> merged.next() 
1 
>>> merged.next() 
'bar' 
>>> merged.next() 
2 
>>> merged.next() 
Traceback (most recent call last): 
    ... 
StopIteration 

¿Cuál es la forma más simple y concisa para hacer esto?

+0

No utilice la gente ésta: 'lista ((rendimiento siguiente (c)) o i para i en los puntos) ' –

Respuesta

36

Un generador resolverá su problema muy bien.

def imerge(a, b): 
    for i, j in itertools.izip(a,b): 
     yield i 
     yield j 
+10

Debe agregar un descargo de responsabilidad: esto solo funcionará si la lista a es finita. – Claudiu

+0

itertools importación def Imerge (a, b): para i, j en zip (a, b): rendimiento i rendimiento j c = itertools.count (1) artículos = [ 'foo', 'bar'] para i en imerge (c, elementos): imprimir i Estoy intentando esto, y esto todavía funciona. len (lista comprimida) = min (l1, l2). Por lo tanto, no se requiere la restricción de longitud finita. – Pramod

+2

Claudiu es correcto. Intente comprimir dos generadores infinitos: al final se quedará sin memoria. Preferiría usar itertools.izip en lugar de zip. Luego construyes el zip sobre la marcha, en lugar de todo a la vez. Todavía tienes que tener cuidado con los bucles infinitos, pero oye. –

10

Haría algo como esto. Esto será más eficiente en tiempo y espacio, ya que no tendrá la ventaja de comprimir objetos juntos. Esto también funcionará si ambos a y b son infinitos.

def imerge(a, b): 
    i1 = iter(a) 
    i2 = iter(b) 
    while True: 
     try: 
      yield i1.next() 
      yield i2.next() 
     except StopIteration: 
      return 
+0

El try/except aquí rompe el protocolo del iterador al silenciar la StopIteration, ¿no es así? –

+0

@David Eyk: está bien, porque volver de un generador plantea StopIteration de todos modos. La declaración try en este caso es superflua. – efotinis

+0

@efotinis: No sabía esto. ¡Gracias! –

7

Puede utilizar zip, así como itertools.chain. Esto sólo funcionan si la primera lista es finita :

merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))]) 
+1

¿Por qué tiene una restricción en el tamaño de la primera lista? – Pramod

+5

No tiene que ser tan complicado, sin embargo: 'merged = chain.from_iterable (izip (items, count (1)))' lo hará. – intuited

15

Puede hacer algo que es casi exaclty lo que sugirió por primera vez @Pramod.

def izipmerge(a, b): 
    for i, j in itertools.izip(a,b): 
    yield i 
    yield j 

La ventaja de este enfoque es que no se quedará sin memoria si tanto a como b son infinitos.

+0

Bastante correcto, David. @Pramod cambió su respuesta para usar izip antes de que yo notara el tuyo, ¡pero gracias! –

0

¿Por qué se necesitan itertools?

def imerge(a,b): 
    for i,j in zip(a,b): 
     yield i 
     yield j 

En este caso al menos uno de A o B debe ser de longitud finita, causa postal devolverá una lista, no un iterador. Si necesita un iterador como resultado, puede elegir la solución de Claudiu.

+0

Prefiero un iterador, ya que estoy leyendo valores de archivos de tamaño arbitrario. Estoy seguro de que hay casos en que el zip es superior. –

3

No estoy seguro de cuál es su aplicación, pero puede encontrar la función enumerate() más útil.

>>> items = ['foo', 'bar', 'baz'] 
>>> for i, item in enumerate(items): 
... print item 
... print i 
... 
foo 
0 
bar 
1 
baz 
2 
+0

¡Siempre me olvido de enumerar! Qué pequeña herramienta útil, aunque no funcionará en mi aplicación particular. ¡Gracias! –

9

También estoy de acuerdo en que itertools no es necesario.

¿Pero por qué detenerse en 2?

def tmerge(*iterators): 
    for values in zip(*iterators): 
     for value in values: 
     yield value 

maneja cualquier cantidad de iteradores desde 0 en adelante.

ACTUALIZACIÓN: DOH! Un comentarista señaló que esto no funcionará a menos que todos los iteradores tengan la misma longitud.

El código correcto es:

def tmerge(*iterators): 
    empty = {} 
    for values in itertools.izip_longest(*iterators, fillvalue=empty): 
    for value in values: 
     if value is not empty: 
     yield value 

y sí, yo sólo lo intentó con listas de longitud desigual, y una lista que contiene {}.

+0

¿Esto agota cada iterador? Creo que zip se truncará al más corto. Estoy buscando una fusión que tome uno de cada iterador, hasta que cada uno de ellos se agote. –

+0

Qué embarazoso. ¡Estás en lo correcto! Vea mi código mejorado aquí. –

+1

¡No se necesita vergüenza, su respuesta y respuesta rápida me salvó horas de dolor! –

0

Usando itertools.izip(), en lugar de zip() como en algunas de las otras respuestas, mejorará el rendimiento:

Como "pydoc itertools.izip" muestra: "funciona como la función zip(), pero consume menos memoria al devolver un iterador en lugar de una lista ".

Itertools.izip también funcionará correctamente incluso si uno de los iteradores es infinito.

1

Uso IZIP y la cadena juntos:

>>> list(itertools.chain.from_iterable(itertools.izip(items, c))) # 2.6 only 
['foo', 1, 'bar', 2] 

>>> list(itertools.chain(*itertools.izip(items, c))) 
['foo', 1, 'bar', 2] 
0

Un método concisa es utilizar una expresión generador con itertools.cycle(). Evita la creación de una cadena larga() de tuplas.

generator = (it.next() for it in itertools.cycle([i1, i2])) 
3

prefiero esta otra forma que es mucho más concisa:

iter = reduce(lambda x,y: itertools.chain(x,y), iters) 
3

Una de las características menos conocidas de Python es que usted puede tener más de cláusulas en una expresión de generador. Muy útil para aplanar listas anidadas, como las que obtienes de zip()/izip().

def imerge(*iterators): 
    return (value for row in itertools.izip(*iterators) for value in row) 
+1

Definitivamente funcionaría, aunque las expresiones del generador anidado son menos legibles. Utilizaría este estilo si estuviera preocupado por el rendimiento. –

+0

Es muy conciso, como suele ser Python, pero ¿cómo se empieza a ver lo que hace este código? ¿Cuál es el efecto de 'value for row in ...' seguido de 'for value in row'? ¿No es esto una lista anidada-generador de comprensión? ¿No debería terminar con algo así como 'para rowvalue in row' o está' value' sombreado? –

+0

@StevenLu Básicamente son dos bucles anidados, como este: 'para la fila en itertools.izip (* iterators): para el valor en la fila: yield value ' –

3

Aquí es una solución elegante:

def alternate(*iterators): 
    while len(iterators) > 0: 
     try: 
      yield next(iterators[0]) 
      # Move this iterator to the back of the queue 
      iterators = iterators[1:] + iterators[:1] 
     except StopIteration: 
      # Remove this iterator from the queue completely 
      iterators = iterators[1:] 

Utilizando una cola real para un mejor rendimiento (según lo sugerido por David):

from collections import deque 

def alternate(*iterators): 
    queue = deque(iterators) 
    while len(queue) > 0: 
     iterator = queue.popleft() 
     try: 
      yield next(iterator) 
      queue.append(iterator) 
     except StopIteration: 
      pass 

funciona incluso cuando algunos iteradores son finitos y otros son infinitos:

from itertools import count 

for n in alternate(count(), iter(range(3)), count(100)): 
    input(n) 

Lienzo:

0 
0 
100 
1 
1 
101 
2 
2 
102 
3 
103 
4 
104 
5 
105 
6 
106 

También se detiene correctamente si/cuando se han agotado todos los iteradores.

Si desea manejar iterables no iterador, como listas, puede utilizar

def alternate(*iterables): 
    queue = deque(map(iter, iterables)) 
    ... 
+0

Un enfoque interesante. :) Tantas formas de hacer esto. Me pregunto si un 'deque()' rotativo sería más eficiente que reconstruir la tupla en cada iteración. –

+0

@DavidEyk Eso suena como una buena idea. – user76284

+0

Notable. Incluso mejor que la receta de los documentos itertools. – Dubslow

Cuestiones relacionadas