scala - withFilter en lugar de filtro
lazy-evaluation scala-collections (5)
Además de la excelente respuesta de Shadowlands , me gustaría brindar un ejemplo intuitivo de la diferencia entre filter
y withFilter
.
Consideremos el siguiente código
val list = List(1, 2, 3)
var go = true
val result = for(i <- list; if(go)) yield {
go = false
i
}
La mayoría de las personas esperan que el result
sea igual a la List(1)
. Este es el caso desde Scala 2.8, porque la comprensión se traduce en
val result = list withFilter {
case i => go
} map {
case i => {
go = false
i
}
}
Como puede ver, la traducción convierte la condición en una llamada a withFilter
. Anterior Scala 2.8, for-comprensión se tradujo en algo como lo siguiente:
val r2 = list filter {
case i => go
} map {
case i => {
go = false
i
}
}
Usando el filter
, el valor del result
sería bastante diferente: List(1, 2, 3)
. El hecho de que estamos haciendo que el indicador de go
false
no tiene ningún efecto en el filtro, porque el filtro ya está hecho. Nuevamente, en Scala 2.8, este problema se resuelve usando withFilter
. Cuando se usa withFilter
, la condición se evalúa cada vez que se accede a un elemento dentro de un método de map
.
Referencia : - p.120, Scala en acción (cubre Scala 2.10), Manning Publications, Milanjan Raychaudhuri - Pensamientos de Odersky sobre la traducción de la comprensión
¿Es siempre más eficaz usar con filtro en lugar de filtro, cuando luego se aplican funciones como mapa, mapa plano, etc.?
¿Por qué solo se admiten map, flatmap y foreach? (Funciones esperadas como forall / exists también)
Como solución alternativa, puede implementar otras funciones solo con map
y flatMap
.
Además, esta optimización es inútil en colecciones pequeñas ...
Nota: la diferencia entre
c filter p
yc withFilter p
es que la primera crea una nueva colección, mientras que la segunda solo restringe el dominio demap
subsiguiente,flatMap
,foreach
ywithFilter
operaciones dewithFilter
.
Así que el filter
tomará la colección original y producirá una nueva colección, pero con withFilter
no pasará (por pereza) de forma no estricta los valores sin filtrar a las llamadas de map
/ map
/ flatMap
withFilter
, guardando una segunda pasada a través de la colección (filtrada). Por lo tanto, será más eficiente al pasar a estas llamadas a métodos posteriores.
De hecho, withFilter
está específicamente diseñado para trabajar con cadenas de estos métodos, que es lo que se withFilter
para la comprensión. No se requieren otros métodos (como forall
/ exists
) para esto, por lo que no se han agregado al tipo de withFilter
de withFilter
.
La razón principal porque forall / exists no está implementado es que el caso de uso es que:
- puede aplicar de forma perezosa con Filter a una secuencia infinita / iterable
- puede aplicar otra forma perezosa con Filter (y una y otra vez)
Para implementar for all / exists necesitamos obtener todos los elementos, perdiendo la pereza.
Así por ejemplo:
import scala.collection.AbstractIterator
class RandomIntIterator extends AbstractIterator[Int] {
val rand = new java.util.Random
def next: Int = rand.nextInt()
def hasNext: Boolean = true
}
//rand_integers is an infinite random integers iterator
val rand_integers = new RandomIntIterator
val rand_naturals =
rand_integers.withFilter(_ > 0)
val rand_even_naturals =
rand_naturals.withFilter(_ % 2 == 0)
println(rand_even_naturals.map(identity).take(10).toList)
//calling a second time we get
//another ten-tuple of random even naturals
println(rand_even_naturals.map(identity).take(10).toList)
Tenga en cuenta que ten_rand_even_naturals sigue siendo un iterador. Solo cuando llamemos a List, los números aleatorios serán generados y filtrados en cadena
Tenga en cuenta que el mapa (identidad) es equivalente al mapa (i => i) y se usa aquí para convertir un objeto con filtro de nuevo al tipo original (por ejemplo, una colección, un flujo, un iterador)
Usar para el rendimiento puede ser una solución, por ejemplo:
for {
e <- col;
if e isNotEmpty
} yield e.get(0)