Inicio / Elixir / Elixir: Programación Funcional y Concurrente / Colecciones

Colecciones

Listas, tuplas, keyword lists, maps, MapSet y ranges.

Principiante

Colecciones en Elixir

Las colecciones son estructuras de datos fundamentales en Elixir. A diferencia de otros lenguajes, todas las colecciones en Elixir son inmutables: cada operación que "modifica" una colección en realidad crea una nueva. Elixir ofrece varias estructuras optimizadas para diferentes casos de uso.

Listas

Las listas en Elixir son listas enlazadas. Son eficientes para prepend (agregar al inicio) pero costosas para acceso por índice:

lista = [1, 2, 3, 4, 5]

# Prepend (O(1))
nueva = [0 | lista]   # => [0, 1, 2, 3, 4, 5]

# Head y Tail
[cabeza | cola] = [1, 2, 3]
cabeza  # => 1
cola    # => [2, 3]

# Concatenación
[1, 2] ++ [3, 4]      # => [1, 2, 3, 4]

# Diferencia
[1, 2, 3] -- [2]      # => [1, 3]

# Funciones comunes
length([1, 2, 3])      # => 3
Enum.at([10, 20, 30], 1) # => 20
List.first([1, 2, 3])   # => 1
List.last([1, 2, 3])    # => 3

Tuplas

Las tuplas almacenan elementos contiguos en memoria. Son eficientes para acceso por índice pero costosas para modificación:

tupla = {:ok, "resultado", 42}

# Acceso por índice
elem(tupla, 0)         # => :ok
elem(tupla, 1)         # => "resultado"

# Tamaño
tuple_size(tupla)      # => 3

# Modificar (crea nueva tupla)
put_elem(tupla, 1, "nuevo")  # => {:ok, "nuevo", 42}

# Uso típico como retorno de funciones
case File.read("archivo.txt") do
  {:ok, contenido} -> IO.puts(contenido)
  {:error, razon}  -> IO.puts("Error: #{razon}")
end

Keyword Lists

Las keyword lists son listas de tuplas de dos elementos donde el primer elemento es un átomo. Permiten claves duplicadas y mantienen el orden:

# Sintaxis completa y abreviada
opciones = [{:puerto, 4000}, {:host, "localhost"}]
opciones = [puerto: 4000, host: "localhost"]  # Equivalente

# Acceso
opciones[:puerto]      # => 4000

# Claves duplicadas permitidas
config = [param: 1, param: 2]
Keyword.get_values(config, :param)  # => [1, 2]

# Se usan mucho como último argumento de funciones
String.split("a b c", " ", trim: true)

# Funciones del módulo Keyword
Keyword.put(opciones, :ssl, true)
Keyword.has_key?(opciones, :puerto)  # => true

Maps

Los maps son la colección clave-valor principal en Elixir. No permiten claves duplicadas y no garantizan orden:

# Crear un map
usuario = %{nombre: "Carlos", edad: 28, activo: true}

# Acceso con átomos
usuario.nombre          # => "Carlos"
usuario[:edad]          # => 28

# Acceso con cualquier tipo de clave
datos = %{"clave" => "valor", 42 => "número"}
datos["clave"]          # => "valor"
datos[42]               # => "número"

# Actualizar (crea nuevo map)
actualizado = %{usuario | edad: 29}
# => %{nombre: "Carlos", edad: 29, activo: true}

# Agregar clave nueva
Map.put(usuario, :email, "carlos@mail.com")

# Funciones útiles
Map.keys(usuario)       # => [:activo, :edad, :nombre]
Map.values(usuario)     # => [true, 28, "Carlos"]
Map.merge(%{a: 1}, %{b: 2})  # => %{a: 1, b: 2}
Map.drop(usuario, [:activo])  # => %{edad: 28, nombre: "Carlos"}

Pattern Matching con Maps

Una característica poderosa es que puedes hacer match parcial con maps:

%{nombre: nombre} = %{nombre: "Ana", edad: 25}
nombre  # => "Ana"

# En funciones
defmodule Saludador do
  def saludar(%{nombre: nombre, idioma: "es"}), do: "¡Hola, #{nombre}!"
  def saludar(%{nombre: nombre, idioma: "en"}), do: "Hello, #{nombre}!"
  def saludar(%{nombre: nombre}), do: "Hi, #{nombre}!"
end

MapSet

MapSet implementa un conjunto (set) donde cada elemento es único:

set = MapSet.new([1, 2, 3, 2, 1])
# => MapSet.new([1, 2, 3])

MapSet.put(set, 4)
# => MapSet.new([1, 2, 3, 4])

MapSet.member?(set, 2)   # => true

# Operaciones de conjuntos
a = MapSet.new([1, 2, 3])
b = MapSet.new([2, 3, 4])

MapSet.union(a, b)        # => MapSet.new([1, 2, 3, 4])
MapSet.intersection(a, b) # => MapSet.new([2, 3])
MapSet.difference(a, b)   # => MapSet.new([1])

Ranges

Los ranges representan secuencias de números:

rango = 1..10

Enum.to_list(1..5)        # => [1, 2, 3, 4, 5]
Enum.to_list(1..10//3)    # => [1, 4, 7, 10] (con paso)

3 in 1..10                # => true
15 in 1..10               # => false

# Uso con Enum
Enum.map(1..5, &(&1 * 2))  # => [2, 4, 6, 8, 10]
Enum.sum(1..100)            # => 5050

Resumen

Elixir proporciona colecciones especializadas para cada necesidad: listas enlazadas para procesamiento secuencial, tuplas para grupos pequeños de tamaño fijo, keyword lists para opciones con orden, maps para datos estructurados clave-valor, MapSet para conjuntos sin duplicados y ranges para secuencias numéricas. La inmutabilidad de todas estas estructuras garantiza la seguridad en entornos concurrentes y facilita el razonamiento sobre el código.

Ejercicio de práctica

Colecciones de Elixir

Practica con listas, tuplas, maps y keyword lists.

# list_operations/1 → recibe lista, retorna mapa con operaciones
# %{head: primer_elem, tail: resto, length: longitud, reversed: lista_invertida}
# Si lista vacía: %{head: nil, tail: [], length: 0, reversed: []}

# keyword_get/3 → obtiene valor de keyword list con default
# keyword_get([name: "Ana", age: 25], :name, nil) → "Ana"
# keyword_get([name: "Ana"], :city, "Unknown") → "Unknown"

# map_transform/2 → transforma valores de un mapa con función
# map_transform(%{a: 1, b: 2}, fn x -> x * 2 end) → %{a: 2, b: 4}