¿Cuál es la diferencia básica entre doblar y reducir en Kotlin? ¿Cuándo usar cuál?

131

Estoy bastante confundido con estas dos funciones fold()y reduce()en Kotlin, ¿alguien puede darme un ejemplo concreto que las distinga a ambas?

TapanHP
fuente
2
doblar y reducir .
Zoe
44
Eche un vistazo a esto para una discusión fundamental profunda de este tema
GhostCat
2
@LunarWatcher, vi esos documentos, pero no lo entiendo, esa es tu pregunta publicada, ¿puedes dar un ejemplo?
TapanHP
1
@MattKlein hecho
Jayson Minard

Respuestas:

281

fold toma un valor inicial, y la primera invocación de la lambda que le pase recibirá ese valor inicial y el primer elemento de la colección como parámetros.

Por ejemplo, tome el siguiente código que calcula la suma de una lista de enteros:

listOf(1, 2, 3).fold(0) { sum, element -> sum + element }

La primera llamada a la lambda será con parámetros 0y 1.

Tener la capacidad de pasar un valor inicial es útil si tiene que proporcionar algún tipo de valor o parámetro predeterminado para su operación. Por ejemplo, si estaba buscando el valor máximo dentro de una lista, pero por alguna razón desea devolver al menos 10, puede hacer lo siguiente:

listOf(1, 6, 4).fold(10) { max, element ->
    if (element > max) element else max
}

reduceno toma un valor inicial, sino que comienza con el primer elemento de la colección como el acumulador (llamado sumen el siguiente ejemplo).

Por ejemplo, hagamos una suma de enteros nuevamente:

listOf(1, 2, 3).reduce { sum, element -> sum + element }

La primera llamada a la lambda aquí será con parámetros 1y 2.

Puede usarlo reducecuando su operación no dependa de ningún valor distinto de los de la colección a la que lo está aplicando.

zsmb13
fuente
47
¡Buena explicación! Yo también diría que esa colección vacía no se puede reducir, pero se puede doblar.
Miha_x64
mire, a un nivel muy principiante en Kotlin, el primer ejemplo que dio puede explicarlo más con algunos pasos y la respuesta final? sería una gran ayuda
TapanHP
3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }producirá una excepción, pero emptyList<Int>().fold(0) { acc, s -> acc + s }está bien.
Miha_x64
31
reduce también obliga al retorno de lambda a ser del mismo tipo que los miembros de la lista, lo cual no es cierto con fold. Esta es una consecuencia importante de hacer que el primer elemento de la lista, el valor inicial del acumulador.
andresp
44
@andresp: solo como una nota de integridad: no tiene que ser del mismo tipo. Los miembros de la lista también pueden ser un subtipo del acumulador: esto funciona listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(el tipo de lista es Int, mientras que el tipo de acumulador se declara como Number y en realidad es Long)
Boris
11

La principal diferencia funcional que llamaría (que se menciona en los comentarios en la otra respuesta, pero puede ser difícil de entender) es que reduce arrojará una excepción si se realiza en una colección vacía.

listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.

Esto se debe a .reduceque no sabe qué valor devolver en caso de "sin datos".

Compare esto con .fold, que requiere que proporcione un "valor inicial", que será el valor predeterminado en el caso de una colección vacía:

val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)

Entonces, incluso si no desea agregar su colección a un solo elemento de un tipo diferente (no relacionado) (que solo .foldle permitirá hacerlo), si su colección inicial puede estar vacía, entonces debe verificar su colección tamaño primero y luego .reduce, o simplemente use.fold

val collection: List<Int> = // collection of unknown size

val result1 = if (collection.isEmpty()) 0
              else collection.reduce { x, y -> x + y }

val result2 = collection.fold(0) { x, y -> x + y }

assertEquals(result1, result2)
Matt Klein
fuente