Estoy bastante confundido con ambas funciones fold() y reduce() en Kotlin, ¿alguien puede darme un ejemplo concreto que las distinga a ambas?
Respuesta sencilla
El resultado de reducir y plegar es "una lista de elementos se transformará en un solo elemento ".
En caso de pliegue , proporcionamos 1 parámetro adicional además de la lista, pero en caso de reducción , solo se considerarán los elementos de la lista.
Pliegue
listOf("AC","Fridge").fold("stabilizer") { freeGift, itemBought -> freeGift + itemBought } //output: stabilizerACFridgeEn el caso anterior, piense como aire acondicionado, refrigerador comprado en la tienda y le dan estabilizador como regalo (este será el parámetro pasado en el pliegue). Entonces, obtendrá los 3 artículos juntos. Tenga en cuenta que freeGift estará disponible solo una vez, es decir, para la primera iteración.
Reducir
En caso de reducción, obtenemos los elementos de la lista como parámetros y podemos realizar las transformaciones requeridas en ellos.
listOf("AC","Fridge").reduce { itemBought1, itemBought2 -> itemBought1 + itemBought2 } //output: ACFridgeOtra diferencia que ninguna de las otras respuestas menciona es la siguiente:
El resultado de una operación de reduce siempre será del mismo tipo (o supertipo) que los datos que se están reduciendo. Podemos ver que a partir de la definición del método reduce :
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S { val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.") var accumulator: S = iterator.next() while (iterator.hasNext()) { accumulator = operation(accumulator, iterator.next()) } return accumulator }Por otro lado, el resultado de una operación de plegado puede ser cualquier cosa, ya que no hay restricciones a la hora de configurar el valor inicial. Entonces, por ejemplo, digamos que tenemos una cadena que contiene letras y dígitos. Queremos calcular la suma de todos los dígitos. Podemos hacer eso fácilmente con fold:
val string = "1a2b3" val result: Int = string.fold(0, { currentSum: Int, char: Char -> if (char.isDigit()) currentSum + Character.getNumericValue(char) else currentSum }) //result is equal to 6reduce : el método reduce() transforma una colección dada en un solo resultado .
val numbers: List<Int> = listOf(1, 2, 3) val sum: Int = numbers.reduce { acc, next -> acc + next } //sum is 6 now. fold - ¿Qué pasaría en el caso anterior de una lista vacía ? En realidad, no hay un valor correcto para devolver, por lo que reduce() lanza una RuntimeException
En este caso, el fold es una herramienta útil. Puedes ponerle un valor inicial -
val sum: Int = numbers.fold(0, { acc, next -> acc + next }) Aquí, hemos proporcionado valor inicial. Por el contrario, para reduce() , si la colección está vacía , se devolverá el valor inicial, lo que evitará que RuntimeException .
fold toma un valor inicial, y la primera invocación de la lambda que le pases 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 números enteros:
listOf(1, 2, 3).fold(0) { sum, element -> sum + element } La primera llamada a la lambda será con los parámetros 0 y 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 quiere devolver al menos 10, podría hacer lo siguiente:
listOf(1, 6, 4).fold(10) { max, element -> if (element > max) element else max } reduce no toma un valor inicial, sino que comienza con el primer elemento de la colección como acumulador (llamado sum en el siguiente ejemplo).
Por ejemplo, volvamos a hacer una suma de enteros:
listOf(1, 2, 3).reduce { sum, element -> sum + element } La primera llamada a la lambda aquí será con los parámetros 1 y 2 .
Puede usar reduce cuando su operación no depende de ningún otro valor que no sean los de la colección a la que lo está aplicando.
La principal diferencia funcional que mencionaría (que se menciona en los comentarios sobre la otra respuesta, pero puede ser difícil de entender) es que reduce generará 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 que .reduce 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 caso de una colección vacía:
val result = listOf<Int>().fold(0) { x, y -> x + y } assertEquals(0, result) Por lo tanto, incluso si no desea agregar su colección a un solo elemento de un tipo diferente (no relacionado) (que solo .fold le permitirá hacer), si su colección inicial puede estar vacía, entonces debe verificar primero el tamaño de su colección 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)