2010-01-07 9 views
9

¿Cuál es la forma más eficiente de alternar tomando valores de diferentes iteradores en Python, para que, por ejemplo, alternate(xrange(1, 7, 2), xrange(2, 8, 2)) arroje 1, 2, 3, 4, 5, 6. Conozco una forma de implementarlo sería:Alternando entre iteradores en Python

def alternate(*iters): 
    while True: 
     for i in iters: 
      try: 
       yield i.next() 
      except StopIteration: 
       pass 

Pero, ¿hay una manera más eficiente o más limpia? (O, mejor aún, una función itertools me perdí?)

+0

Qué quiere decir 1, 2, 7, 8, 2, 2? –

+1

Pregunta muy similar: http://stackoverflow.com/questions/243865/how-do-i-merge-two-python-iterators – Seth

+0

@Sean Devlin: No, 1 2 3 4 5 6 es correcto. http://docs.python.org/library/functions.html#xrange –

Respuesta

6

¿qué tal zip? También puede intentar IZIP de itertools

>>> zip(xrange(1, 7, 2),xrange(2, 8 , 2)) 
[(1, 2), (3, 4), (5, 6)] 

si esto no es lo que desea, por favor, dar más ejemplos en tu post cuestión.

+0

Por sí solo, izip no es suficiente para mis propósitos. Pero poner una cadena alrededor de izip, como 'chain.from_iterable (izip (iterables))' funciona. Supongo que eso es lo bueno de los iterables. ¡Gracias! – LeafStorm

+0

El problema con 'zip' en este caso es que evaluará los iteradores inmediatamente, lo que le obligará a renunciar a la semántica del generador. – Sapph

+0

Lo cual sería especialmente problemático teniendo en cuenta que mi caso de uso original para esto eran iteradores que generaban un número infinito de valores. – LeafStorm

2

Se podría definir alternate así:

import itertools 
def alternate(*iters): 
    for elt in itertools.chain.from_iterable(
     itertools.izip(*iters)): 
     yield elt 

print list(alternate(xrange(1, 7, 2), xrange(2, 8, 2))) 

Esto deja abierta la cuestión de qué hacer si uno se detiene antes de que otro repetidor. Si desea continuar hasta que se agote el iterador más largo, puede usar itertools.izip_longest en lugar de itertools.izip.

import itertools 
def alternate(*iters): 
    for elt in itertools.chain.from_iterable(
     itertools.izip_longest(*iters)): 
     yield elt 
print list(alternate(xrange(1, 7, 2), xrange(2, 10, 2))) 

Esto pondrá rendimiento

[1, 2, 3, 4, 5, 6, None, 8] 

Nota None se produjo cuando el xrange iterador (1,7,2) plantea StopIteration (no tiene más elementos).

Si desea omita el iterador en lugar de ceder None, usted puede hacer esto:

Dummy=object() 

def alternate(*iters): 
    for elt in itertools.chain.from_iterable(
     itertools.izip_longest(*iters,fillvalue=Dummy)): 
     if elt is not Dummy: 
      yield elt 
+0

Podría ser más limpio para definir el maniquí como 'Maniquí = objeto()' –

+0

@Chris: ¡Gracias! Hice el cambio. – unutbu

15

Para una implementación "limpia", desea

itertools.chain(*itertools.izip(*iters)) 

pero tal vez quieren

itertools.chain(*itertools.izip_longest(*iters)) 
+2

¿Esto agota el generador antes de pasarlo a izip y luego encadenar? – Dan

+0

@Dan Si 'iters' era un generador, lo agotaría, al menos en python 2; ejecutando 'def z (* iters): print (type (iters)); print (iters) 'then' z (* (i para i en xrange (1,18))) 'imprime' 'y' (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17) '. – Abbafei

1

Si son de la misma longitud, itertools.izip, se pueden aprovechar de esta manera:

def alternate(*iters): 
    for row in itertools.izip(*iters): 
     for i in row: 
      yield i 
6

Ver roundrobinin the itertools "Recipes" section. Es una versión más general de alternativa.

def roundrobin(*iterables): 
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C" 
    # Recipe credited to George Sakkis 
    pending = len(iterables) 
    nexts = cycle(iter(it).__next__ for it in iterables) 
    while pending: 
     try: 
      for next in nexts: 
       yield next() 
     except StopIteration: 
      pending -= 1 
      nexts = cycle(islice(nexts, pending)) 
1

Hay dos problemas con su intento:

  1. No envuelva cada objeto en iters con iter() por lo que fallará con iterables como list; y
  2. Por pass ing en StopIteration su generador es un bucle infinito.

Algunos código simple que hace resuelve ambos esas cuestiones y sigue siendo fácil de leer y comprender:

def alternate(*iters): 
    iters = [iter(i) for i in iters] 
    while True: 
     for i in iters: 
      yield next(i) 

>>> list(alternate(range(1, 7, 2), range(2, 8, 2))) 
[1, 2, 3, 4, 5, 6]