Pregunta

Tengo dos iteradores, un list y un itertools.count (es decir, un generador de valores infinitos). Me gustaría fusionar estos dos en un iterador resultante que alternará los valores de rendimiento entre los dos:

>>> 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 sencilla y concisa de hacer esto?

¿Fue útil?

Solución

Un generador resolverá tu problema muy bien.

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

Otros consejos

Puedes hacer algo que es casi exactamente lo que @Pramod sugirió por primera vez.

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.

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 número 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í, lo probé con listas de longitud desigual y una lista que contiene {}.

Haría algo como esto. Esto será más eficiente en tiempo y espacio, ya que no tendrá la sobrecarga de comprimir objetos juntos. Esto también funcionará si tanto a como 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

Puede usar zip así como itertools.chain . Esto solo funcionará si la primera lista es finita :

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

No estoy seguro de cuál es tu aplicación, pero puede que encuentres 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

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

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

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

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

Aquí hay 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:]

Uso de una cola real para un mejor rendimiento (como lo sugiere 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 infinitos:

from itertools import count

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

Impresiones:

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

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

Si desea manejar iterables que no sean iteradores, como listas, puede usar

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

Use izip y encadéntelos 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]

¿Por qué se necesitan los componentes?

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, porque zip devolverá una lista, no un iterador. Si necesita un iterador como salida, puede optar por la solución Claudiu.

Usar 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.

Un método conciso es usar una expresión generadora con itertools.cycle (). Evita crear una cadena larga () de tuplas.

generator = (it.next() for it in itertools.cycle([i1, i2]))
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top