2010-02-23 11 views
27

Digamos que tengo una función de generador que se parece a esto:índice y la rebanada de un generador en Python

def fib(): 
    x,y = 1,1 
    while True: 
     x, y = y, x+y 
     yield x 

Idealmente, tan sólo pudiera utilizar fib() [10] o fib() [2 : 12: 2] para obtener índices y sectores, pero actualmente tengo que usar itertools para estas cosas. No puedo usar un generador para reemplazar las listas.

creo que la solución será para envolver fib() en una clase:

class Indexable(object): 
    .... 

fib_seq = Indexable(fib()) 

Lo que debe indexable parecerse a hacer este trabajo?

Respuesta

33
import itertools 

class Indexable(object): 
    def __init__(self,it): 
     self.it = iter(it) 
    def __iter__(self): 
     return self.it 
    def __getitem__(self,index): 
     try: 
      return next(itertools.islice(self.it,index,index+1)) 
     except TypeError: 
      return list(itertools.islice(self.it,index.start,index.stop,index.step)) 

Se puede utilizar de esta manera:

it = Indexable(fib()) 
print(it[10]) 
#144 
print(it[2:12:2]) 
#[610, 1597, 4181, 10946, 28657] 

en cuenta que it[2:12:2] no devuelve [3, 8, 21, 55, 144] desde el repetidor ya se había avanzado 11 elementos a causa de la llamada a it[10].

Editar: Si desea volver a it[2:12:2][3, 8, 21, 55, 144] entonces tal vez utilizar esto en su lugar:

class Indexable(object): 

    def __init__(self, it): 
     self.it = iter(it) 
     self.already_computed = [] 

    def __iter__(self): 
     for elt in self.it: 
      self.already_computed.append(elt) 
      yield elt 

    def __getitem__(self, index): 
     try: 
      max_idx = index.stop 
     except AttributeError: 
      max_idx = index 
     n = max_idx - len(self.already_computed) + 1 
     if n > 0: 
      self.already_computed.extend(itertools.islice(self.it, n)) 
     return self.already_computed[index] 

Esta versión guarda los resultados en self.already_computed y utiliza esos resultados si es posible. De lo contrario, calcula más resultados hasta que tenga suficientes para devolver el elemento indexado o slice.

+0

Para exhibir el mismo comportamiento que una lista, __getitem__ tendría que rebobinar el generador. ¿Hay una manera fácil de hacer eso? –

+0

No sé si hay alguna forma de hacerlo, fácil o no, pero la clase indexable podría almacenar todos los elementos ya generados en una lista. Luego '__getitem__' sacaría los números directamente de la lista, después de avanzar primero el generador si es necesario. – MatrixFrog

+0

Gracias MatrixFrog; eso es lo que terminé haciendo. – unutbu

0

Así basa fuera del código del ~ unutbu y la adición de un poco de itertools.tee:

import itertools 

class Indexable(object): 
    def __init__(self, it): 
     self.it = it 

    def __iter__(self): 
     self.it, cpy = itertools.tee(self.it) 
     return cpy 

    def __getitem__(self, index): 
     self.it, cpy = itertools.tee(self.it) 
     if type(index) is slice: 
      return list(itertools.islice(cpy, index.start, index.stop, index.step)) 
     else: 
      return next(itertools.islice(cpy, index, index+1)) 
+0

Correcto, pero esta opción quema CPU cada vez que la usa. Para listas que son demasiado largas para almacenar en 'un_computado' de' unutbu, el tiempo de CPU requerido aquí también es excesivo. –

+0

Creo que también podría reducirse a la complejidad del generador, si el código dentro del generador es muy pesado, usar ya_computado es la mejor opción, pero quizás si es simple, como xrange, simplemente recomprándolo es mejor. –

+0

¿No itertools.tee internamente solo almacena los resultados para que pueda reproducirlos más tarde? No tiene forma de saber si puede copiar el iterador tampoco; considere un iterador sobre las solicitudes de red entrantes, o algo. – Ben

0

Si se trata de una rebanada 1-uso que usted puede simplemente usar el método escrito por ~ unutbu. Si necesita cortar varias veces, deberá almacenar todos los valores intermedios para poder "rebobinar" el iterador. Como los iteradores pueden iterar cualquier cosa, no tendrían un método de rebobinado por defecto.

Además, dado que un iterador de rebobinado tiene que almacenar todos los resultados intermedios que lo haría (en la mayoría de los casos) no tienen ningún beneficio sobre el simple hecho de hacer list(iterator)

Básicamente ... que o bien no es necesario un repetidor, o' no es lo suficientemente específico sobre la situación.

+3

'list (fib())' le dará un MemoryError finalmente –

+0

¿Qué tal el caso en que quiero el número 6.000 de Fibonacci? La forma más clara de expresar esto es fib [6000] aunque hay varias formas de obtenerlo, simplemente no se expresan con elegancia. –

+0

Mi punto es que, en casi todas las situaciones de la vida real, harías que el objeto se pudiera cortar en lugar de implementar el corte en el generador. Si no lo hace, sería imposible optimizar la función de llamada correctamente. Para cualquier número grande de Fibonacci no querría generar toda la lista solo para obtener un número, usaría un método para "adivinar" el número. – Wolph

0

Aquí está la respuesta de ~ unutbu modificada a la lista de subclase. Obviamente abuso como append, insert etc. producirá resultados extraños!

lo hace llegar __str__ y __repr__ métodos para gratis, aunque

import itertools 
class Indexable(list): 
    def __init__(self,it): 
     self.it=it 
    def __iter__(self): 
     for elt in self.it: 
      yield elt 
    def __getitem__(self,index): 
     try: 
      max_idx=index.stop 
     except AttributeError: 
      max_idx=index 
     while max_idx>=len(self): 
      self.append(next(self.it)) 
     return list.__getitem__(self,index) 
+1

Las listas son muy diferentes de lo que queremos para que esto sea útil. No hagas esto – fletom

+0

Ok, pero ¿quién es "_we_"? –

+3

Las personas que tienen este problema, supongo. – fletom

Cuestiones relacionadas