Ambas interfaces definen solo un método
public operator fun iterator(): Iterator<T>
La documentación dice que Sequence
está destinado a ser vago. ¿Pero no es Iterable
perezoso también (a menos que esté respaldado por una Collection
)?
Ambas interfaces definen solo un método
public operator fun iterator(): Iterator<T>
La documentación dice que Sequence
está destinado a ser vago. ¿Pero no es Iterable
perezoso también (a menos que esté respaldado por una Collection
)?
Respuestas:
La diferencia clave radica en la semántica y la implementación de las funciones de extensión stdlib para Iterable<T>
y Sequence<T>
.
Porque Sequence<T>
, las funciones de extensión funcionan de manera perezosa cuando es posible, de manera similar a las operaciones intermedias de Java Streams . Por ejemplo, Sequence<T>.map { ... }
devuelve otro Sequence<R>
y en realidad no procesa los elementos hasta que se llama a una operación de terminal como toList
o fold
.
Considere este código:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Imprime:
before sum 1 2
Sequence<T>
está diseñado para un uso diferido y una canalización eficiente cuando desea reducir el trabajo realizado en las operaciones de la terminal tanto como sea posible, al igual que Java Streams. Sin embargo, la pereza introduce algunos gastos generales, que no son deseables para las transformaciones simples comunes de colecciones más pequeñas y las hace menos eficaces.
En general, no hay una buena manera de determinar cuándo se necesita, por lo que en Kotlin stdlib la pereza se hace explícita y se extrae a la Sequence<T>
interfaz para evitar usarla en todos los Iterable
s por defecto.
Porque Iterable<T>
, por el contrario, las funciones de extensión con semántica de operación intermedia trabajan con entusiasmo, procesan los elementos de inmediato y devuelven otro Iterable
. Por ejemplo, Iterable<T>.map { ... }
devuelve a List<R>
con los resultados de la asignación.
El código equivalente para Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Esto imprime:
1 2 before sum
Como se dijo anteriormente, Iterable<T>
no es perezoso de forma predeterminada, y esta solución se muestra bien: en la mayoría de los casos, tiene una buena localidad de referencia , aprovechando así el caché de la CPU, la predicción, la búsqueda previa, etc., de modo que incluso la copia múltiple de una colección sigue funcionando bien. suficiente y funciona mejor en casos simples con colecciones pequeñas.
Si necesita más control sobre la canalización de evaluación, hay una conversión explícita a una secuencia perezosa con Iterable<T>.asSequence()
función.
map
, filter
y otras no contienen suficiente información para decidir más allá del tipo de colección de origen, y dado que la mayoría de las colecciones también son Iterables, ese no es un buen marcador para "ser perezoso" porque es comúnmente EN TODAS PARTES. perezoso debe ser explícito para estar seguro.
Completando la respuesta de la tecla de acceso rápido:
Es importante notar cómo Sequence e Iterable iteran a través de sus elementos:
Ejemplo de secuencia:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Resultado del registro:
filtro - Mapa - Cada uno; filtro - Mapa - Cada
Ejemplo iterable:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filtro - filtro - Mapa - Mapa - Cada uno - Cada
Iterable
se asigna a lajava.lang.Iterable
interfaz en elJVM
, y se implementa mediante colecciones de uso común, como List o Set. Las funciones de extensión de la colección en estos se evalúan con entusiasmo, lo que significa que todos procesan inmediatamente todos los elementos en su entrada y devuelven una nueva colección que contiene el resultado.Aquí hay un ejemplo simple del uso de las funciones de colección para obtener los nombres de las primeras cinco personas en una lista cuya edad es al menos 21:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Plataforma de destino: JVMRunning en kotlin v. 1.3.61 Primero, la verificación de edad se realiza para cada persona en la lista, con el resultado en una lista nueva. Luego, el mapeo a sus nombres se realiza para cada Persona que permaneció después del operador de filtro, terminando en otra lista nueva (ahora es a
List<String>
). Finalmente, hay una última lista nueva creada para contener los primeros cinco elementos de la lista anterior.En contraste, Sequence es un concepto nuevo en Kotlin para representar una colección de valores evaluados de forma perezosa. Las mismas extensiones de colección están disponibles para la
Sequence
interfaz, pero estas devuelven inmediatamente instancias de Secuencia que representan un estado procesado de la fecha, pero sin procesar ningún elemento. Para comenzar a procesar, elSequence
tiene que ser terminado con un operador de terminal, estos son básicamente una solicitud a la Secuencia para materializar los datos que representa en alguna forma concreta. Los ejemplos incluyentoList
,toSet
ysum
, por mencionar solo algunos. Cuando se llaman, solo se procesará el número mínimo requerido de elementos para producir el resultado exigido.Transformar una colección existente en una secuencia es bastante sencillo, solo necesita usar la
asSequence
extensión. Como se mencionó anteriormente, también debe agregar un operador de terminal; de lo contrario, la secuencia nunca realizará ningún procesamiento (nuevamente, ¡perezoso!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Plataforma de destino: JVMRunning en kotlin v. 1.3.61 En este caso, cada una de las instancias de Person en la Secuencia se verifica por su edad, si pasan, se extrae su nombre y luego se agrega a la lista de resultados. Esto se repite para cada persona en la lista original hasta que se encuentran cinco personas. En este punto, la función toList devuelve una lista y el resto de las personas en el
Sequence
no se procesan.También hay algo extra de lo que es capaz una secuencia: puede contener una cantidad infinita de elementos. Con esto en perspectiva, tiene sentido que los operadores trabajen de la manera que lo hacen: un operador en una secuencia infinita nunca podría regresar si hiciera su trabajo con entusiasmo.
Como ejemplo, aquí hay una secuencia que generará tantas potencias de 2 como requiera su operador de terminal (ignorando el hecho de que esto se desbordaría rápidamente):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Puedes encontrar más aquí .
Java
(en su mayoríaGuava
)