Kotlin

Kotlin

Tutorial Kotlin: Listas, Conjuntos y Mapas

Explora en Kotlin el uso de listas, conjuntos y mapas, aprendiendo sus características inmutables y mutables, y técnicas para su manipulación eficiente.

Aprende Kotlin GRATIS y certifícate

Definición y uso de listas (List, MutableList)

En Kotlin, una lista es una colección ordenada de elementos que permite acceso indexado. Existen dos tipos principales: List, que es inmutable, y MutableList, que puede ser modificada después de su creación.

La interfaz List<T> representa una colección de elementos de solo lectura. Una vez creada, no es posible añadir ni eliminar elementos. Para crear una lista inmutable, se utiliza la función listOf():

val numeros: List<Int> = listOf(1, 2, 3, 4, 5)

En este ejemplo, numeros es una lista inmutable de enteros. Intentar modificarla resultará en un error de compilación, garantizando la inmutabilidad de la colección.

Por otro lado, MutableList<T> es una interfaz que permite modificar su contenido. Para crear una lista mutable, se utiliza la función mutableListOf():

val letras: MutableList<Char> = mutableListOf('a', 'b', 'c')

Con letras, es posible añadir, eliminar o actualizar elementos según sea necesario:

letras.add('d')          // Añade 'd' al final de la lista
letras.remove('b')       // Elimina 'b' de la lista
letras[0] = 'z'          // Reemplaza el elemento en la posición 0 por 'z'

El acceso a los elementos de una lista se realiza mediante índices, comenzando desde cero. Para obtener un elemento específico, se utiliza la sintaxis de corchetes:

val primerNumero = numeros[0]    // Accede al primer elemento de 'numeros'

Es importante manejar adecuadamente los índices para evitar excepciones como IndexOutOfBoundsException.

Las listas en Kotlin también proporcionan diversas funciones para facilitar su manipulación. Por ejemplo, para verificar si un elemento existe en la lista, se puede usar contains:

if ('a' in letras) {
    println("La lista contiene la letra 'a'")
}

Para conocer el tamaño de la lista, se utiliza la propiedad size:

println("La lista tiene ${letras.size} elementos")

Es posible crear listas vacías cuando no se conocen los elementos de antemano. Para una lista inmutable vacía:

val listaVacia: List<String> = listOf()

Y para una lista mutable vacía:

val listaMutableVacia: MutableList<String> = mutableListOf()

Al trabajar con listas, es común iterar sobre sus elementos utilizando bucles. El bucle for es especialmente útil para este propósito:

for (letra in letras) {
    println(letra)
}

Este código recorrerá cada elemento de letras y lo imprimirá en pantalla.

Otra característica esencial es la posibilidad de combinar diferentes tipos en una lista mediante el uso de tipos genéricos o la clase Any. Sin embargo, es recomendable especificar el tipo de datos para mantener la seguridad de tipo y evitar errores en tiempo de ejecución.

val mixto: List<Any> = listOf(1, "dos", 3.0)

Aunque es posible, se debe tener cuidado al manejar listas con elementos de diferentes tipos.

Finalmente, es relevante mencionar que, aunque las listas inmutables no pueden ser modificadas, es posible crear una nueva lista basada en una existente utilizando funciones como plus:

val nuevaLista = numeros.plus(6)    // Crea una nueva lista con el elemento '6' añadido

De esta manera, se mantiene la inmutabilidad de la lista original mientras se genera una versión ampliada.

Definición y uso de conjuntos (Set, MutableSet)

En Kotlin, un conjunto es una colección de elementos únicos que no mantiene un orden específico. Existen dos tipos principales: Set, que es inmutable, y MutableSet, que permite modificaciones después de su creación.

La interfaz Set<T> representa una colección de solo lectura donde cada elemento es único. Para crear un conjunto inmutable, se utiliza la función setOf():

val numeros: Set<Int> = setOf(1, 2, 3, 4, 5)

En este ejemplo, numeros es un conjunto inmutable de enteros. Los elementos duplicados se ignoran, asegurando la unicidad dentro del conjunto.

Si se necesita un conjunto mutable, capaz de añadir o eliminar elementos, se emplea la función mutableSetOf():

val letras: MutableSet<Char> = mutableSetOf('a', 'b', 'c')

Con letras, es posible modificar el contenido del conjunto de la siguiente manera:

letras.add('d')       // Añade 'd' al conjunto
letras.remove('b')    // Elimina 'b' del conjunto

Es importante destacar que, al añadir elementos repetidos en un conjunto, estos no se duplicarán. Por ejemplo:

letras.add('a')       // 'a' ya existe en el conjunto, no se añade de nuevo

Para verificar si un elemento específico pertenece al conjunto, se utiliza la expresión in:

if ('c' in letras) {
    println("El conjunto contiene la letra 'c'")
}

Los conjuntos no tienen un orden predefinido, por lo que al iterar sobre ellos, el orden de los elementos puede no ser el mismo que el de inserción. Para recorrer un conjunto, se puede utilizar un bucle for:

for (letra in letras) {
    println(letra)
}

La propiedad size permite conocer el tamaño del conjunto, es decir, el número de elementos únicos que contiene:

println("El conjunto tiene ${letras.size} elementos")

Para crear conjuntos vacíos, se especifica el tipo genérico de la siguiente manera:

val conjuntoVacio: Set<String> = setOf()
val conjuntoMutableVacio: MutableSet<String> = mutableSetOf()

Es posible realizar operaciones de conjuntos como unión, intersección y diferencia utilizando funciones integradas. Por ejemplo:

val numerosPares = setOf(2, 4, 6)
val numerosImpares = setOf(1, 3, 5)
val union = numerosPares.union(numerosImpares)              // Unión de dos conjuntos
val interseccion = numerosPares.intersect(numerosImpares)   // Intersección (vacía en este caso)

Estas operaciones facilitan el manejo de colecciones de elementos únicos en distintas situaciones.

Para convertir otros tipos de colecciones en conjuntos y eliminar elementos duplicados, se puede utilizar la función toSet():

val listaConDuplicados = listOf(1, 2, 2, 3, 3, 3)
val conjuntoSinDuplicados = listaConDuplicados.toSet()   // Resultado: {1, 2, 3}

Los conjuntos son especialmente útiles cuando se requiere garantizar que no haya elementos repetidos y el orden de los elementos no es relevante.

Finalmente, aunque Set es inmutable, es posible crear nuevos conjuntos basados en otros utilizando operaciones como plus o minus:

val nuevoConjunto = numeros.plus(6)    // Añade '6', creando un nuevo conjunto
val conjuntoReducido = numeros.minus(1) // Elimina '1', creando un nuevo conjunto

De esta forma, se mantiene la inmutabilidad del conjunto original mientras se generan nuevas versiones modificadas.

Definición y uso de mapas (Map, MutableMap)

En Kotlin, un mapa es una colección que asocia claves únicas con valores. Es ideal para almacenar pares clave-valor donde cada clave permite acceder a su valor correspondiente. Existen dos tipos principales de mapas: Map, que es inmutable, y MutableMap, que permite modificaciones después de su creación.

La interfaz Map<K, V> representa un mapa de solo lectura. Una vez creado, no es posible añadir ni eliminar entradas. Para crear un mapa inmutable, se utiliza la función mapOf():

val capitales: Map<String, String> = mapOf(
    "España" to "Madrid",
    "Francia" to "París",
    "Italia" to "Roma"
)

En este ejemplo, capitales es un mapa inmutable que relaciona países con sus capitales. La sintaxis "clave" to "valor" permite definir cada par clave-valor de forma clara.

Si se necesita un mapa que pueda ser modificado, se emplea la función mutableMapOf() para crear un MutableMap:

val edades: MutableMap<String, Int> = mutableMapOf(
    "Ana" to 28,
    "Luis" to 34,
    "María" to 22
)

Con edades, es posible añadir, actualizar o eliminar entradas según sea necesario:

edades["Carlos"] = 30            // Añade una nueva entrada al mapa
edades["Luis"] = 35              // Actualiza el valor asociado a la clave "Luis"
edades.remove("Ana")             // Elimina la entrada con la clave "Ana"

Para acceder al valor asociado a una clave específica, se utiliza la sintaxis de corchetes o el método get():

val edadMaria = edades["María"]         // Devuelve 22
val edadLuis = edades.get("Luis")       // Devuelve 35

Es importante tener en cuenta que si se intenta acceder a una clave que no existe, el resultado será null. Para manejar esto de manera segura, se puede utilizar el operador elvis ?: o el método getValue() que arroja una excepción si la clave no está presente:

val edadCarlos = edades["Carlos"] ?: 0   // Devuelve 30 si existe, 0 si no

Para comprobar si una clave o un valor existe en el mapa, se utilizan los métodos containsKey() y containsValue():

if (edades.containsKey("María")) {
    println("El mapa contiene la clave 'María'")
}

if (edades.containsValue(34)) {
    println("Alguien tiene 34 años")
}

La propiedad size permite conocer el número de entradas en el mapa:

println("El mapa tiene ${edades.size} entradas")

Es posible iterar sobre los mapas utilizando bucles for. Se pueden recorrer las claves, los valores o ambos:

for ((nombre, edad) in edades) {
    println("$nombre tiene $edad años")
}

for (nombre in edades.keys) {
    println("Clave: $nombre")
}

for (edad in edades.values) {
    println("Valor: $edad")
}

Al crear mapas vacíos, es necesario especificar los tipos genéricos:

val mapaVacio: Map<Int, String> = mapOf()
val mapaMutableVacio: MutableMap<Int, String> = mutableMapOf()

Los mapas soportan diversas operaciones útiles. Por ejemplo, para combinar dos mapas y crear uno nuevo:

val mapa1 = mapOf(1 to "uno", 2 to "dos")
val mapa2 = mapOf(3 to "tres", 4 to "cuatro")
val mapaCombinado = mapa1 + mapa2   // Combina ambos mapas

Para eliminar entradas de un mapa inmutable y crear uno nuevo sin ciertas claves:

val mapaReducido = mapaCombinado - 1   // Elimina la entrada con clave 1

En mapas mutables, es posible utilizar métodos como putAll() para añadir múltiples entradas:

val nuevosDatos = mapOf("Pedro" to 29, "Luisa" to 25)
edades.putAll(nuevosDatos)

Si se necesita realizar una acción específica cuando se accede a una clave ausente, se puede utilizar el método getOrDefault():

val edadJuan = edades.getOrDefault("Juan", 0)   // Devuelve 0 si "Juan" no está en el mapa

Es posible convertir un mapa en otras colecciones. Por ejemplo, para obtener una lista de pares clave-valor:

val listaDePares = edades.toList()   // Devuelve una lista de Pair<String, Int>

O para convertir claves y valores en listas separadas:

val listaDeNombres = edades.keys.toList()
val listaDeEdades = edades.values.toList()

Cuando se trabaja con mapas, es fundamental recordar que las claves deben ser únicas. Si se añade una entrada con una clave existente en un MutableMap, el valor anterior será reemplazado

Para evitar sobrescribir valores, se puede comprobar previamente si la clave existe:

if ("Luisa" !in edades) {
    edades["Luisa"] = 25
}

Los mapas son altamente versátiles y se utilizan en numerosas situaciones donde se necesita una asociación entre claves y valores. Comprender su uso y funciones disponibles es esencial para manejar colecciones en Kotlin de manera eficiente.

Operaciones comunes sobre colecciones (filtrar, mapear, agrupar)

Las colecciones en Kotlin ofrecen funciones de orden superior que facilitan su manipulación y transformación. Entre las operaciones más utilizadas se encuentran filtrar, mapear y agrupar, las cuales permiten procesar y reorganizar los datos de manera eficiente.

El filtrado consiste en obtener elementos que cumplen una condición específica. La función filter devuelve una nueva colección con los elementos que satisfacen un predicado dado:

val numeros = listOf(1, 2, 3, 4, 5, 6)
val numerosPares = numeros.filter { it % 2 == 0 }  // Resultado: [2, 4, 6]

En este ejemplo, numerosPares contiene solo los números que son divisibles por 2. Es posible utilizar filterNot para obtener los elementos que no cumplen la condición:

val numerosImpares = numeros.filterNot { it % 2 == 0 }  // Resultado: [1, 3, 5]

Para trabajar con listas que pueden contener valores nulos, se emplea filterNotNull, que elimina los elementos nulos de la colección:

val listaConNulos = listOf(1, null, 2, null, 3)
val listaSinNulos = listaConNulos.filterNotNull()  // Resultado: [1, 2, 3]

El mapeo transforma cada elemento de una colección aplicando una función y devuelve una nueva colección con los resultados. La función map es esencial para esta operación:

val cuadrados = numeros.map { it * it }  // Resultado: [1, 4, 9, 16, 25, 36]

Aquí, cada número en numeros se eleva al cuadrado. Si se desea transformar y a la vez filtrar elementos nulos o realizar una transformación que pueda devolver nulos, se utiliza mapNotNull:

val cadenas = listOf("1", "2", "a", "3")
val numerosEnteros = cadenas.mapNotNull { it.toIntOrNull() }  // Resultado: [1, 2, 3]

En este caso, toIntOrNull() intenta convertir cada cadena a entero y devuelve null si falla. mapNotNull excluye esos nulos del resultado final.

La agrupación organiza los elementos de una colección en un mapa según un criterio especificado. La función groupBy es fundamental para esta tarea:

data class Persona(val nombre: String, val edad: Int)

val personas = listOf(
    Persona("Ana", 28),
    Persona("Luis", 34),
    Persona("María", 28),
    Persona("Pedro", 34)
)

val personasPorEdad = personas.groupBy { it.edad }
// Resultado: {28=[Persona(nombre=Ana, edad=28), Persona(nombre=María, edad=28)], 34=[Persona(nombre=Luis, edad=34), Persona(nombre=Pedro, edad=34)]}

En el ejemplo anterior, las personas se han agrupado por edad, creando un mapa donde cada clave es una edad y el valor es una lista de personas con esa edad.

Además, es posible transformar los elementos al agrupar utilizando groupBy con dos parámetros:

val nombresPorEdad = personas.groupBy(
    keySelector = { it.edad },
    valueTransform = { it.nombre }
)
// Resultado: {28=["Ana", "María"], 34=["Luis", "Pedro"]}

Así, se obtiene un mapa que asocia edades con los nombres de las personas que tienen esa edad.

Otra operación útil es associateBy, que crea un mapa a partir de una colección, donde las claves se definen mediante una función y los valores son los elementos originales:

val personasPorNombre = personas.associateBy { it.nombre }
// Resultado: {"Ana"=Persona(nombre=Ana, edad=28), "Luis"=Persona(nombre=Luis, edad=34), ...}

Si se desea modificar los valores del mapa, se puede utilizar associateBy con un parámetro adicional:

val edadesPorNombre = personas.associateBy(
    keySelector = { it.nombre },
    valueTransform = { it.edad }
)
// Resultado: {"Ana"=28, "Luis"=34, "María"=28, "Pedro"=34}

La función partition divide una colección en dos listas según un predicado. Es útil cuando se necesita separar elementos que cumplen una condición de los que no:

val (menoresDe30, mayoresDe30) = personas.partition { it.edad < 30 }
// menoresDe30: [Persona(nombre=Ana, edad=28), Persona(nombre=María, edad=28)]
// mayoresDe30: [Persona(nombre=Luis, edad=34), Persona(nombre=Pedro, edad=34)]

Con flatMap, se puede transformar cada elemento de una colección en una colección y luego concatenarlas en una sola. Es especialmente útil para manipular colecciones anidadas:

val listasDeNumeros = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
val todosLosNumeros = listasDeNumeros.flatMap { it }  // Resultado: [1, 2, 3, 4, 5, 6]

El uso de estas operaciones optimiza el procesamiento de datos en Kotlin, permitiendo escribir código más conciso y legible. Es importante familiarizarse con ellas para aprovechar al máximo las capacidades de las colecciones en este lenguaje.

Aprende Kotlin GRATIS online

Ejercicios de esta lección Listas, Conjuntos y Mapas

Evalúa tus conocimientos de esta lección Listas, Conjuntos y Mapas con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de Kotlin

Accede a todas las lecciones de Kotlin y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Kotlin

Kotlin

Introducción Y Entorno

Instalación Y Primer Programa De Kotlin

Kotlin

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

Kotlin

Sintaxis

Operadores Y Expresiones

Kotlin

Sintaxis

Cadenas De Texto Y Manipulación

Kotlin

Sintaxis

Estructuras De Control

Kotlin

Sintaxis

Funciones Y Llamada De Funciones

Kotlin

Sintaxis

Clases Y Objetos

Kotlin

Programación Orientada A Objetos

Herencia Y Polimorfismo

Kotlin

Programación Orientada A Objetos

Interfaces Y Clases Abstractas

Kotlin

Programación Orientada A Objetos

Data Classes Y Destructuring

Kotlin

Programación Orientada A Objetos

Tipos Genéricos Y Varianza

Kotlin

Programación Orientada A Objetos

Listas, Conjuntos Y Mapas

Kotlin

Estructuras De Datos

Introducción A La Programación Funcional

Kotlin

Programación Funcional

Funciones De Primera Clase Y De Orden Superior

Kotlin

Programación Funcional

Inmutabilidad Y Datos Inmutables

Kotlin

Programación Funcional

Composición De Funciones

Kotlin

Programación Funcional

Monads Y Manejo Funcional De Errores

Kotlin

Programación Funcional

Operaciones Funcionales En Colecciones

Kotlin

Programación Funcional

Transformaciones En Monads Y Functors

Kotlin

Programación Funcional

Funciones Parciales Y Currificación

Kotlin

Programación Funcional

Introducción A Las Corutinas

Kotlin

Coroutines Y Asincronía

Asincronía Con Suspend, Async Y Await

Kotlin

Coroutines Y Asincronía

Concurrencia Funcional

Kotlin

Coroutines Y Asincronía

Evaluación

Kotlin

Evaluación

Accede GRATIS a Kotlin y certifícate

Certificados de superación de Kotlin

Supera todos los ejercicios de programación del curso de Kotlin y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender la diferencia entre List y MutableList.
  2. Conocer cómo crear y manipular conjuntos con Set y MutableSet.
  3. Aprender a utilizar Map y MutableMap para gestionar pares clave-valor.
  4. Aplicar operaciones de transformación como filtrado, mapeo y agrupación sobre colecciones.