Fold o Reduce en Kotlin: la eterna pregunta de los principiantes
\n\nSi llevas al menos una semana programando en Kotlin, seguro que ya te has topado con las funciones fold y reduce. A primera vista, hacen lo mismo: recorren una colección y acumulan un resultado. Pero el diablo, como siempre, está en los detalles. Una elección incorrecta puede provocar errores, excepciones o simplemente código poco elegante.
En este artículo analizaremos la diferencia entre fold y reduce en Kotlin, veremos ejemplos prácticos de proyectos reales y recordaremos para siempre cuándo usar cada función. ¡Allá vamos!
\n\n¿Qué son fold y reduce? Conceptos básicos
\n\nAmbas funciones son operaciones terminales para colecciones. Toman todos los elementos y los reducen a un solo valor. Imagina que tienes una pila de panqueques y quieres obtener una gran hamburguesa. fold y reduce son dos técnicas diferentes para apilarlos.
Reduce: el enfoque minimalista
\n\nLa función reduce usa el primer elemento de la colección como valor inicial del acumulador. Esto significa que simplemente no puede trabajar con una colección vacía: lanzará una UnsupportedOperationException.
val numbers = listOf(1, 2, 3, 4, 5)\nval sum = numbers.reduce { acc, i -> acc + i }\nprintln(sum) // 15\n\nAquí acc inicialmente es 1 (el primer elemento), luego se le suma 2, luego 3, y así sucesivamente. Simple y elegante, pero hay un detalle.
Fold: control sobre el inicio
\n\nfold, a diferencia de reduce, requiere que especifiques explícitamente un valor inicial. Esto te da control total y, lo que es más importante, permite trabajar con colecciones vacías.
val numbers = listOf(1, 2, 3, 4, 5)\nval sum = numbers.fold(0) { acc, i -> acc + i }\nprintln(sum) // 15\n\nval emptyList = listOf<Int>()\nval emptySum = emptyList.fold(0) { acc, i -> acc + i }\nprintln(emptySum) // 0, ¡sin errores!\n\n¿Ves la diferencia? fold devuelve el valor inicial si la colección está vacía. reduce en esa situación fallaría.
Diferencias clave entre fold y reduce en Kotlin
\n\nOrganicemos los conocimientos. Aquí tienes una tabla que te ayudará a orientarte rápidamente:
\n\n- \n
- Valor inicial: En
foldlo defines tú. Enreducees el primer elemento de la colección. \n - Tipo de resultado: En
foldel tipo de resultado puede diferir del tipo de los elementos (por ejemplo, sumas Int y obtienes String). Enreduceel tipo de resultado siempre coincide con el tipo de los elementos. \n - Colección vacía:
foldla maneja correctamente (devuelve el valor inicial).reducelanza una excepción. \n - Rendimiento: Ambas funciones tienen complejidad O(n). La diferencia de rendimiento es imperceptible. \n
Ahora veamos esto en código.
\n\nEjemplo 1: Diferentes tipos de acumulador y elementos
\n\nSupongamos que tenemos una lista de números y queremos obtener una cadena con su suma en formato legible. fold lo hace perfectamente porque el tipo del acumulador (String) es diferente al tipo de los elementos (Int).
val numbers = listOf(1, 2, 3)\nval result = numbers.fold("Suma: ") { acc, i -> "$acc$i " }\nprintln(result) // "Suma: 1 2 3 "\n\nIntenta hacer lo mismo con reduce: el compilador dará un error porque reduce requiere que el tipo del acumulador coincida con el tipo de los elementos.
Ejemplo 2: Cálculo de suma con valor inicial
\n\nUna tarea común: calcular el total de pedidos, pero ya tenemos un saldo inicial en la cuenta.
\n\nval orders = listOf(100, 250, 75)\nval initialBalance = 500\nval totalBalance = orders.fold(initialBa