2012-06-29 8 views
6

Come posso creare un equivalente Python di pdtolist da Pop-11?Elenco dinamico che si espande automaticamente

Si supponga di avere un generatore chiamato g che restituisce (diciamo) numeri interi uno alla volta. Mi piacerebbe costruire un elenco a che cresce automaticamente mentre chiedo valori oltre la fine attuale dell'elenco. Per esempio:

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 

Terminologia - di anticipare una probabile equivoco: una lista è un "array dinamico", ma non è questo quello che voglio dire; Mi piacerebbe una "lista dinamica" in un senso più astratto.

Per spiegare meglio la motivazione, supponiamo di avere 999999999 elementi da elaborare. Cercare di adattare tutti quelli in memoria (in una lista normale) tutto in una volta sarebbe una sfida. Un generatore risolve quella parte del problema presentandoli uno alla volta; ognuno creato su richiesta o letto individualmente dal disco. Ma supponiamo che durante l'elaborazione vuoi fare riferimento ad alcuni valori recenti, non solo a quelli attuali? Potresti ricordare gli ultimi (diciamo) dieci valori in una lista separata. Ma una lista dinamica è migliore, in quanto li ricorda automaticamente.

+0

Sostituire il metodo '__getitem__' della lista per catturare l'oggetto 'IndexError'. –

+0

Quindi hai la lista 'L' ed esegui' L [999999999] '- la lista dovrebbe diventare quella lunghezza? –

+0

Sì, in linea di principio. Programmatore di caveat! –

risposta

2

Mille grazie a tutti coloro che hanno contribuito con le idee! Ecco cosa ho raccolto insieme da tutte le risposte. Ciò mantiene la maggior parte delle funzionalità dalla normale classe di lista, aggiungendo ulteriori comportamenti laddove necessario per soddisfare requisiti aggiuntivi.

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))) 

I valori generati in precedenza sono accessibili singolarmente come elementi o sezioni. La cronologia registrata si espande se necessario se gli articoli richiesti sono oltre la fine attuale dell'elenco. È possibile accedere all'intera cronologia registrata in una volta sola, utilizzando print a o assegnata a un elenco normale utilizzando b = a[:]. Una porzione della cronologia registrata può essere cancellata usando del a[0:4]. È possibile scorrere l'intero elenco utilizzando for, eliminando mentre si va, o ogni volta che si adatta. Se si raggiunge la fine dei valori generati, viene generato il numero StopIteration.

Rimane qualche imbarazzo. Assegnazioni come a = a[0:4] troncano correttamente la cronologia, ma l'elenco risultante non si espande automaticamente. Utilizzare invece del a[0:4] per mantenere le proprietà di crescita automatiche. Inoltre, non sono completamente felice di dover riconoscere un valore magico, 2147483647, che rappresenta l'elemento più recente.

2

Questo potrebbe iniziare:

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) 

Avrai bisogno di aggiungere un po 'una gestione speciale per le fette (attualmente, ma restituisce un elenco normale, in modo da perdere il comportamento dinamico). Inoltre, se vuoi che il generatore stesso sia un elemento della lista, questo aggiungerà un po 'di complessità.

+0

Questo non dovrebbe essere una sottoclasse di lista; il '__init__' non è compatibile e non dovrebbe supportare' __len__' o '__setitem__' ... – agf

+0

@agf: Il problema' __init__' è un punto valido, ma perché non dovrebbe supportare '__len__' o' __setitem__'? – voithos

+0

La sua lunghezza è sconosciuta perché la lunghezza del generatore è sconosciuta e dovrebbe riflettere solo gli elementi del generatore, quindi non dovresti essere in grado di assegnarli. – agf

1

Ho appena risposto ad un'altra domanda simile e ho deciso di aggiornare la mia risposta per voi come questo?

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()) 
Problemi correlati