2012-06-29 8 views
6

¿Cómo puedo hacer un Python equivalente a pdtolist desde Pop-11?Lista dinámica que se expande automáticamente

Supongamos que tengo un generador llamado g que devuelve (digamos) números enteros de a uno por vez. Me gustaría construir una lista a que crece automáticamente a medida que solicito valores más allá del final actual de la lista. Por ejemplo:

print a # => [ 0, 1, 2, g] 
print a[0] # => 0 
print a[1] # => 1 
print a[2] # => 2 
# (obvious enough up to here) 

print a[6] # => 6 
print a # => [ 0, 1, 2, 3, 4, 5, 6, g] 
# list has automatically expanded 

a = a[4:] # discard some previous values 
print a # => [ 4, 5, 6, g] 
print a[0] # => 4 

Terminología - anticipar un malentendido probable: una lista es una "matriz dinámica" pero eso no es lo que quiero decir; Me gustaría una "lista dinámica" en un sentido más abstracto.

Para explicar mejor la motivación, suponga que tiene 999999999 elementos para procesar. Intentar encajar todos esos en la memoria (en una lista normal) de una sola vez sería un desafío. Un generador resuelve esa parte del problema presentándolos uno a la vez; cada uno creado a pedido o leído individualmente desde el disco. Pero supongamos que durante el procesamiento desea hacer referencia a algunos valores recientes, no solo a los actuales. Podría recordar los últimos (digamos) diez valores en una lista separada. Pero una lista dinámica es mejor, ya que los recuerda automáticamente.

+0

Reemplace el método '__getitem__' de la lista para capturar el' IndexError'. –

+0

Entonces, tiene la lista 'L' y realiza' L [999999999] '- ¿la lista debería tener esa longitud? –

+0

Sí, en principio. ¡Caveat Programmor! –

Respuesta

2

Muchas gracias a todos los que contribuyeron ideas! Esto es lo que he reunido de todas las respuestas. Esto retiene la mayor parte de la funcionalidad de la clase de lista normal, agregando comportamientos adicionales cuando es necesario para cumplir con requisitos adicionales.

class DynamicList(list): 
    def __init__(self, gen): 
     self.gen = gen 

    def __getitem__(self, index): 
     while index >= len(self): 
      self.append(next(self.gen)) 
     return super(DynamicList, self).__getitem__(index) 

    def __getslice__(self, start, stop): 
     # treat request for "last" item as "most recently fetched" 
     if stop == 2147483647: stop = len(self) 
     while stop > len(self): 
      self.append(next(self.gen)) 
     return super(DynamicList, self).__getslice__(start, stop) 

    def __iter__(self): 
     return self 

    def next(self): 
     n = next(self.gen) 
     self.append(n) 
     return n 

a = DynamicList(iter(xrange(10))) 

Los valores generados previamente se pueden acceder individualmente como elementos o sectores. El historial grabado se expande según sea necesario si los elementos solicitados están más allá del final actual de la lista. Se puede acceder a todo el historial grabado de una sola vez, usando print a, o asignándolo a una lista normal usando b = a[:]. Una parte del historial grabado se puede eliminar usando del a[0:4]. Puede iterar sobre toda la lista usando for, borrando sobre la marcha, o siempre que lo desee. Si llega al final de los valores generados, se genera StopIteration.

Queda algo de torpeza. Asignaciones como a = a[0:4] truncan con éxito el historial, pero la lista resultante ya no se expande automáticamente. En su lugar, use del a[0:4] para conservar las propiedades de crecimiento automático. Además, no estoy completamente satisfecho con tener que reconocer un valor mágico, 2147483647, que representa el elemento más reciente.

2

Esto podría empezar:

class DynamicList(list): 
    def __init__(self, gen): 
     self._gen = gen 

    def __getitem__(self, index): 
     while index >= len(self): 
      self.append(next(self._gen)) 
     return super(DynamicList, self).__getitem__(index) 

Tendrá que añadir un poco de manejo especial para rodajas (en la actualidad, sólo devuelven una lista normal, por lo que pierden el comportamiento dinámico). Además, si desea que el generador en sí sea un elemento de la lista, eso agregará un poco de complejidad.

+0

Esto no debe ser una subclase de lista; el '__init__' es incompatible y no debería ser compatible con' __len__' o '__setitem__' ... – agf

+0

@agf: El problema' __init__' es un punto válido, pero ¿por qué no debería soportar '__len__' o' __setitem__'? – voithos

+0

Se desconoce su longitud porque se desconoce la longitud del generador y solo debe reflejar elementos del generador, por lo que no debería poder asignarlo. – agf

1

Acabo de responder otra pregunta similar y decidí actualizar mi respuesta ¿cómo es esto?

class dynamic_list(list): 
    def __init__(self,num_gen): 
     self._num_gen = num_gen 
    def __getitem__(self,index): 
     if isinstance(index, int): 
      self.expandfor(index) 
      return super(dynamic_list,self).__getitem__(index) 

     elif isinstance(index, slice): 
      if index.stop<index.start: 
       return super(dynamic_list,self).__getitem__(index) 
      else: 
       self.expandfor(index.stop if abs(index.stop)>abs(index.start) else index.start) 
      return super(dynamic_list,self).__getitem__(index) 

    def __setitem__(self,index,value): 
     if isinstance(index, int): 
      self.expandfor(index) 
      return super(dynamic_list,self).__setitem__(index,value) 

     elif isinstance(index, slice): 
      if index.stop<index.start: 
       return super(dynamic_list,self).__setitem__(index,value) 
      else: 
       self.expandfor(index.stop if abs(index.stop)>abs(index.start) else index.start) 
      return super(dynamic_list,self).__setitem__(index,value) 

    def expandfor(self,index): 
      rng = [] 
      if abs(index)>len(self)-1: 
       if index<0: 
        rng = xrange(abs(index)-len(self)) 
       else: 
        rng = xrange(abs(index)-len(self)+1) 
      for i in rng: 
       self.append(self._num_gen.next()) 
Cuestiones relacionadas