2010-09-24 13 views
6

Voglio un modo conveniente per generare un Iterable, dato un oggetto iniziale e una funzione per produrre l'oggetto successivo da quello corrente, che consuma memoria O (1) (cioè non memorizza i vecchi risultati, se si desidera ripetere una seconda volta, la funzione deve essere nuovamente applicata).Creazione di un memoriale O (1) Iterable da un oggetto iniziale e una funzione che genera l'oggetto successivo, in Scala

Non sembra che ci sia il supporto di libreria per questo. In Scala 2.8, il metodo scala.collection.Iterable.iterate ha la firma

def iterate [A] (start: A, len: Int)(f: (A) ⇒ A) : Iterable[A] 

quindi richiede di specificare quante applicazioni algoritmo iterativo che ti interessa prima del tempo, e la mia comprensione della documentazione è che Iterable.iterate calcola in realtà tutti questi valori subito. D'altra parte, il metodo scala.collection.Iterator.iterate ha la firma

def iterate [T] (start: T)(f: (T) ⇒ T) : Iterator[T] 

che sembra grande, ma abbiamo solo ottenere un Iterator che non offre tutte le comodità di map, filter e gli amici.

Esiste un metodo biblioteca conveniente produrre quello che voglio?

e se non,

Qualcuno può suggerire il codice 'colloquiale' Scala per fare questo?

In sintesi, in un oggetto iniziale a: A, e una funzione f: A => A, Vorrei una TraversableLike (ad esempio, probabilmente un Iterable) che genera a, f(a), f(f(a)), ..., e utilizza O (1) di memoria, con map, filter ecc. funzioni che restituiscono anche qualcosa che è O (1) in memoria.

+0

Un "indizio": leggendo l'API un po 'di più, sto iniziando a sospettare che una buona risposta menzionerà 'TraversableViewLike', ma sono anche sempre più perplessa. –

+2

Iterator * ha * mappa, filtro e amici ... Sei sicuro che usano più di una memoria costante? – huynhjl

+0

È vero, map e filter e così via sono disponibili su 'Iterator', e non provare nulla di strano come forzare' Iterator'. Ma un "Iterable" sarebbe più conveniente; perché non dovrei aspettarmi di poter usare 'tail' (che, ogni volta che viene chiamato' iterator', dovrebbe rimuovere il primo elemento tramite una chiamata a 'next' prima di restituire' Iterator'), etc? (In effetti, quando ho provato a passare il mio codice da "Iterable's a' Iterator's, era qualcosa che dovevo risolvere.) –

risposta

3

Iterator.iterate demo con filtro:

object I { 
    def main(args:Array[String]) { 
    val mb = 1024 * 1024 
    val gen = Iterator.iterate(new Array[Int](10 * mb)){arr => 
     val res = new Array[Int](10 * mb) 
     arr.copyToArray(res) 
     println("allocated 10mb") 
     res(0) = arr(0) + 1 // store iteration count in first elem of new array 
     res 
    } 
    // take 1 out of 100 
    val gen2 = gen filter (arr => arr(0) % 100 == 0) 
    // print first 10 filtered 
    gen2.take(10).foreach { arr => println("filtered " + arr(0)) } 
    } 
} 

(puo 'non funzionare nel REPL come il passo di stampa può rovinare con gestione della memoria)

JAVA_OPTS="-Xmx128m" scala -cp classes I mostrerà che i lavori di filtraggio ed è artificiale. Se non è stato fatto nella memoria costante, ciò causerebbe un errore di heap (poiché assegna qualcosa come 900 * 10mb).

Utilizzare JAVA_OPTS="-Xmx128m -verbose:gc" scala -cp classes I per visualizzare gli eventi di garbage collection.

+0

Grazie per i dettagli per convincermi che tutto è davvero O (1). Vado a provarlo. –

10

Stream farà quello che vuoi, ma non aggrapparti alle celle; solo scorrere i valori.

È un malinteso tristemente comune che gira attorno al fatto che gli Stream memorizzano intrinsecamente ogni valore che calcolano.

Se si scrive questo:

val s1: Stream[Thing] = initialValue #:: «expression computing next value» 

allora davvero ogni valore prodotto dal flusso viene mantenuta, ma questo non è necessario. Se si scrive:

def s2: Stream[Thing] = initialValue #:: «expression computing next value» 

e se il chiamante solo itera oltre i valori del flusso, ma non ricorda il Value Stream stesso (ogni delle sue cellule cons), quindi si verificherà alcuna ritenzione indesiderata. Naturalmente, in questa formulazione, ogni chiamata crea un nuovo Stream a partire da un valore iniziale fisso. Che non è necessaria:

def s3(start: Thing): Stream[Thing] = start #:: «expression computing next value» 

L'unica cosa che dovete guardare fuori per sta passando un Stream a un metodo. In questo modo si catturerà la testa dello stream trasmesso nel parametro method. Un modo per aggirare questo è elaborare lo stream con codice ricorsivo in coda.

+0

Non capisco - Devo essere in grado di passare questo oggetto intorno ad altri consumatori; cioè, l'altro codice sconosciuto farà effettivamente l'iterazione. Non vedo come posso farlo senza passare un riferimento alla testa del 'Stream'. –

+0

È una limitazione. Come ho detto, dovrai strutturare il codice per passare il 'Stream' attraverso catene ottimizzate per la coda. Ma quel codice "sconosciuto" sa che sta ottenendo un 'Stream', quindi sa che non può mantenere i riferimenti alle sue celle (stream-). –

+0

No, questo davvero non lo farà. Perché il codice "sconosciuto" dovrebbe sapere qualcosa? Se qualcun altro chiama nel mio codice, perché potrebbero non solo trattare il valore restituito come detto "Iterable"? –

2

Iterator è proprio ciò che desideri. E l'iteratore ha map, filter, takeWhile e molti altri metodi che è O (1) in memoria. Non penso che ci sia un altro tipo di raccolta con O (1) in memoria.

1
val it = new Iterable[Int] { 
    def iterator = Iterator.iterate(0)(_+1) 
    override 
    def toString: String = "Infinite iterable" 
} 

non provare su REPL (se non per incorporarlo all'interno di un oggetto o di classe), come REPL cercherà di stamparlo, e non usa toString.

+0

Che stampa "Infinito iterabile" nel bagagliaio. – extempore

+0

@extempore Yay! –

+0

Almeno per quanto ho capito, 'it map {_ + 1} take 5' non termina, tuttavia, dato che' map' proverà a forzare 'Iterable'. –

Problemi correlati