2010-09-24 8 views
6

Quiero una forma conveniente de generar un Iterable, dado un objeto inicial y una función para producir el siguiente objeto de el actual, que consume O (1) memoria (es decir, no almacena en caché los resultados anteriores, si desea iterar por segunda vez, la función debe aplicarse nuevamente).Creando una O (1) -memoria Iterable de un objeto inicial y una función que genera el siguiente objeto, en Scala

Parece que no hay soporte de biblioteca para esto. En Scala 2.8, el método tiene la firma scala.collection.Iterable.iterate

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

por lo que requiere que se especifique el número de aplicaciones de funciones iteradas que le interesa antes de tiempo, y mi comprensión de la documentación es que en realidad Iterable.iterate calcula todos estos valores inmediatamente. Por otra parte, el método tiene la firma scala.collection.Iterator.iterate

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

la que se ve muy bien, pero sólo recibe un Iterator que no ofrece todas las ventajas de map, filter y amigos.

¿Existe un método de biblioteca conveniente para producir lo que quiero?

y si no,

Puede alguien sugerir el código Scala 'coloquial' para hacer esto?

En resumen, dado un objeto inicial a: A, y una función de f: A => A, me gustaría un TraversableLike (por ejemplo, probablemente un Iterable) que genera a, f(a), f(f(a)), ..., y utiliza O (1) de memoria, con map, filter etc. funciones que también devuelven algo que es O (1) en la memoria.

+0

Una "pista": leyendo la API un poco más, estoy empezando a sospechar que una buena respuesta mencionará 'TraversableViewLike', pero también estoy cada vez más perplejo. –

+2

Iterator * tiene * mapa, filtro y amigos ... ¿Estás seguro de que usan más que la memoria constante? – huynhjl

+0

Es cierto, el mapa y el filtro están disponibles en 'Iterator', y no intentes nada tonto como forzar al' Iterator'. Pero un 'Iterable' sería más conveniente; ¿Por qué no debería esperar poder usar 'tail' (que, siempre que se llame a' iterator', debería eliminar el primer elemento mediante una llamada a 'next' antes de devolver' Iterator'), etc.? (De hecho, cuando traté de cambiar mi código de esperar 'Iterable's a' Iterator's, esto era algo que tenía que solucionar.) –

Respuesta

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

(esto puede no funcionar en el REPL como el paso de impresión puede estropear con la gestión de memoria)

JAVA_OPTS="-Xmx128m" scala -cp classes I mostrará que las obras de filtrado y es perezoso. Si no se hizo en la memoria constante eso causaría un error de montón (ya que está asignando algo como 900 * 10mb).

Utilice JAVA_OPTS="-Xmx128m -verbose:gc" scala -cp classes I para ver los eventos de recolección de basura.

+0

Gracias por los detalles para convencerme de que todo es realmente O (1). Voy a probar esto. –

10

Stream hará lo que quiera, simplemente no se aferre a las celdas; solo iterar sobre los valores.

Es una idea errónea tristemente común que flota alrededor de que los flujos inherentemente almacenan en caché cada valor que calculan.

Si se escribe esto:

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

continuación, de hecho cada valor producido por la corriente se mantiene, pero esto no es necesario. Si se escribe:

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

y si la persona que llama sólo itera sobre los valores de la corriente, pero no recuerda el valor corriente en sí (en concreto cualquiera de sus cons cells), no se realizará la retención no deseada. Por supuesto, en esta formulación, cada llamada crea un nuevo Stream a partir de un valor inicial fijo. Eso no es necesario:

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

Lo único que hay que tener en cuenta es pasar un Stream a un método. Al hacerlo, se capturará el encabezado de la secuencia pasada en el parámetro del método. Una forma de evitar esto es procesar la secuencia con código recursivo de cola.

+0

No entiendo, necesito poder pasar este objeto alrededor de otros consumidores; es decir, otro código desconocido realmente hará la iteración. No veo cómo puedo hacer esto sin pasar una referencia al encabezado de 'Stream'. –

+0

Es una limitación. Como dije, tendrás que estructurar el código para pasar el 'Stream' a través de cadenas optimizadas para la cola. Pero ese código "desconocido" sabe que está obteniendo un 'Stream', por lo que sabe que no puede retener las referencias a sus células de cons (stream-). –

+0

No, esto realmente no sirve. ¿Por qué el código "desconocido" sabe algo? Si alguien más llama a mi código, ¿por qué no deberían tratar el valor de retorno como decir "Iterable"? –

2

Iterator es lo que usted desea. Y iterator do tiene map, filter, takeWhile y muchos otros métodos que son O (1) en la memoria. No creo que haya otro tipo de colección con O (1) en la memoria.

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

No lo pruebe en REPL (excepto insertándolo dentro de un objeto o clase), como REPL intentará imprimirlo, y no utiliza toString.

+0

que imprime "Infinite iterable" en el tronco. – extempore

+0

@extempore ¡Yay! –

+0

Por lo menos hasta donde yo entiendo, 'it map {_ + 1} take 5' no terminará, sin embargo, ya que' map' intentará forzar el 'Iterable'. –

Cuestiones relacionadas