2011-02-15 9 views
9

Mi función crea una cadena de generadores:¿Cómo invierto un objeto itertools.chain?

def bar(num): 
    import itertools 
    some_sequence = (x*1.5 for x in range(num)) 
    some_other_sequence = (x*2.6 for x in range(num)) 
    chained = itertools.chain(some_sequence, some_other_sequence) 
    return chained 

Mi función a veces tiene que volver chained en orden inverso. Conceptualmente, la siguiente es lo que me gustaría ser capaz de hacer:

if num < 0: 
    return reversed(chained) 
return chained 

Desafortunadamente:

>>> reversed(chained) 
TypeError: argument to reversed() must be a sequence 

¿Cuáles son mis opciones?

Esto está en algún código de representación gráfica en tiempo real, así que no quiero que sea demasiado complicado/lento.

EDIT: Cuando planteé por primera vez esta pregunta, no había pensado en la reversibilidad de los generadores. Como muchos han señalado, los generadores no se pueden revertir.

De hecho, quiero invertir el contenido aplanado de la cadena; no solo el orden de los generadores

Según las respuestas, no hay una sola llamada que pueda usar para invertir una cadena de itertools, por lo que creo que la única solución es usar una lista, al menos para el caso inverso y quizás para ambas.

+0

Hm, yo habría pensado 'itertools.islice' con un paso negativo habría funcionado, pero resulta que sólo los valores positivos son válidos para esa arg a' islice'. Interesante pregunta. –

+0

No es solo 'encadenado', son sus generadores: ' >>> invertido ((x para x en el rango (5))) ' ' TypeError: argumento para invertir() debe ser una secuencia' –

+0

@jleedev Buen punto ; los generadores no se pueden revertir incluso si hay una forma de invertir una cadena de itertools. No me había dado cuenta de eso antes, pero entiéndelo ahora. Sin embargo, al revés tampoco funciona en un itertools.chain que se compone exclusivamente de listas. –

Respuesta

10
if num < 0: 
    lst = list(chained) 
    lst.reverse() 
    return lst 
else: 
    return chained 

reversed() necesita una secuencia real, ya que itera hacia atrás por el índice, y que no iba a funcionar para un generador (que sólo tiene la noción de elemento "siguiente").

Dado que tendrá que desenrollar todo el generador de todos modos para invertir, la forma más eficiente es leerlo en una lista e invertir la lista en contexto con el método .reverse().

+2

pero esto agotará los generadores ... haciendo inútil su uso. – Ant

+7

Sí, pero es necesario agotar el generador si necesita acceder al último elemento en el caso general (que está implicado al invertir). Sin ver el código real, es imposible proporcionar una mejor solución. – shang

+1

@Ant Como shang, Jochen Ritzel y otros mencionaron, los generadores no se pueden revertir sin agotarlos de todos modos. No lo había considerado cuando planteé la pregunta original. Al menos la solución de shang solo desenrolla los generadores para la condición num <0. –

0

reversed solo funciona en objetos que admiten len e indexación. Primero debe generar todos los resultados de un generador antes de ajustar reversed a su alrededor.

Sin embargo, usted puede fácilmente hacer esto:

def bar(num): 
    import itertools 
    some_sequence = (x*1.5 for x in range(num, -1, -1)) 
    some_other_sequence = (x*2.6 for x in range(num, -1, -1)) 
    chained = itertools.chain(some_other_sequence, some_sequence) 
    return chained 
+0

Los objetos que implementan '__reversed__' también funcionarán con' reverse() ': https://docs.python.org/2/reference/datamodel.html#object.__reversed__ –

0

funciona esto en ti verdadera aplicación?

def bar(num): 
    import itertools 
    some_sequence = (x*1.5 for x in range(num)) 
    some_other_sequence = (x*2.6 for x in range(num)) 
    list_of_chains = [some_sequence, some_other_sequence] 
    if num < 0: 
     list_of_chains.reverse() 
    chained = itertools.chain(*list_of_chains) 
    return chained 
+3

Esto invierte el orden de las secuencias en la cadena, no las secuencias en sí mismas 'L1 = [1,2,3]; L2 = [4,5,6] 'encadena estos dos e invierte el resultado y es' [6,5,4,3,2,1] ', con tu solución sería' [4,5,6, 1,2,3] ', ¿no? –

+2

creo que es lo que significaba la operación ... no hay forma de revertir el orden de los valores devueltos por un generador sin agotarlo ... – Ant

+1

Gracias por la sugerencia. No consideré que los generadores no podían revertirse cuando planteé la pregunta original, así que de hecho me gustaría invertir los contenidos aplanados de la cadena en lugar del orden de los generadores en la cadena misma. –

7

No se pueden invertir los generadores por definición. La interfaz de un generador es el iterador, que es un contenedor que solo admite la iteración directa. Cuando desee invertir un iterador, primero debe recopilar todos sus elementos y luego invertirlos.

Use listas en su lugar o genere las secuencias al revés desde el principio.

0

En teoría no se puede porque los objetos encadenados pueden contener incluso secuencias infinitas como itertools.count(...).

Debería intentar invertir sus generadores/secuencias o usar reversed(iterable) para cada secuencia, si corresponde, y luego encadenarlas al último al primero. Por supuesto, esto depende en gran medida de su caso de uso.

3

itertools.cadena tendría que aplicar __reversed__() (esto sería lo mejor) o __len__() y __getitem__()

Dado que no es así, y no hay incluso una manera de acceder a las secuencias internas que necesita para ampliar la secuencia completa para poder revertirla.

reversed(list(CHAIN_INSTANCE)) 

Sería bueno si la cadena haría __reversed__() disponible cuando todas las secuencias son reversibles, pero en la actualidad no hacer eso. Tal vez usted puede escribir su propia versión de la cadena que hace

1
def reversed2(iter): 
    return reversed(list(iter)) 
Cuestiones relacionadas