Operaciones con colecciones II: Filtrado

Operaciones con colecciones II: Filtrado

Siguiendo con la serie dedicada a las funciones para realizar operaciones sobre colecciones, en esta entrada nos centramos en una de las operaciones más usadas para trabajar con colecciones, las operaciones de filtrado.

La biblioteca estándar contiene un grupo de funciones de extensión que permiten filtrar rápidamente ciertos elementos de una colección. Estas funciones no modifican la colección original, por lo que están disponibles tanto para colecciones mutables como de solo lectura. Normalmente para operar con el resultado del filtrado, éste se debe asignar a una variable.

En Kotlin, las condiciones de filtrado se definen mediante predicados: funciones lambda que toman el elemento de una colección y devuelven un valor booleano, donde true significa que el elemento dado coincide con el predicado y false lo contrario.

La función básica de filtrado es filter(). Cuando se llama con un predicado, filter() devuelve los elementos de la colección que coinciden. Tanto para las listas y los conjuntos (List y Set) la colección resultante es una lista (List), mientras que para los diccionarios (Map) también es un Map.
val numeros = listOf("uno", "dos", "tres", "cuatro")
val letrasMas3 = numeros.filter { it.length > 3 }
println(letrasMas3) // [tres, cuatro]

val numerosDict = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filtroMap = numerosDict.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filtroMap) // {key11=11}

Los predicados en filter() solo pueden verificar los valores de los elementos, por lo que si deseamos usar posiciones de elementos en el filtro, debemos usar filterIndexed() que toma un predicado con dos argumentos: el índice y el valor de un elemento.

Para filtrar colecciones por condiciones negativas, usamos filterNot() que devuelve una lista de elementos para los que el predicado produce false.
val numeros = listOf("uno", "dos", "tres", "cuatro")
val filteredIdx = numeros.filterIndexed { index, s -> (index != 0) && (s.length < 5) }
val filteredNot = numeros.filterNot { it.length <= 3 }

println(filteredIdx) // [dos, tres]
println(filteredNot) // [tres, cuatro]

También hay funciones que filtran elementos de una colección en base a su tipo, como filterIsInstance() y filterNotNull(). filterIsInstance() devuelve elementos de una colección de un tipo determinado mientras que filterNotNull() devuelve todos los elementos que no son nulos:
val num = listOf(null, 1, "dos", 3.0, "cuatro")
num.filterIsInstance<String>().forEach {
    print("${it.toUpperCase()} ") // DOS CUATRO
}

val num2 = listOf(null, "uno", "dos", null)
num2.filterNotNull().forEach {
    print("$it: ${it.length} ") // uno: 3 dos: 3
}

Otra función de filtrado es partition(), que filtra una colección por un predicado y mantiene los elementos que no coinciden en una lista separada. Por lo tanto, tiene un Pair de listas como valor de retorno: la primera lista que contiene los elementos que coinciden con el predicado y la segunda que contiene todo lo demás de la colección original:
val numeros = listOf("uno", "dos", "tres", "cuatro")
val (match, noMatch) = numeros.partition { it.length > 3 }

println(match)   // [tres, cuatro]
println(noMatch) // [uno, dos]

Por otra parte, hay funciones que simplemente prueban un predicado en los elementos de una colección:
  • any() devuelve true si al menos un elemento coincide con el predicado dado.
  • none() devuelve true si ninguno de los elementos coincide con el predicado dado.
  • all() devuelve true si todos los elementos coinciden con el predicado dado. Hay que tener en cuenta que all() devuelve true cuando se le llama con cualquier predicado válido sobre una colección vacía.
val numeros = listOf("uno", "dos", "tres", "cuatro")

println(numeros.any { it.endsWith("s") })  // true
println(numeros.none { it.endsWith("a") }) // true
println(numeros.all { it.endsWith("s") })  // false
println(emptyList<Int>().all { it > 5 })   // true

any() y none() también se pueden usar sin un predicado y, en este caso, simplemente comprueban si la colección está vacía: any() devuelve true si hay elementos y false si no los hay, y none() hace lo contrario:
val numeros = listOf("uno", "dos", "tres", "cuatro")
val vacia = emptyList<String>()

println(numeros.any()) // true
println(vacia.any())   // false

println(numeros.none()) // false
println(vacia.none())   // true

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

I/O: entrada y salida de datos en consola

Lectura y escritura de archivos