Colecciones
Domina el módulo Enum y las comprehensions para manipular datos.
El módulo Enum
El módulo Enum es tu caja de herramientas para trabajar con colecciones (listas, mapas, rangos, etc.):
Funciones básicas
lista = [1, 2, 3, 4, 5]
# Contar elementos
iex> Enum.count(lista)
5
# Verificar si está vacía
iex> Enum.empty?(lista)
false
# Primer y último elemento
iex> Enum.at(lista, 0)
1
iex> Enum.at(lista, -1) # Índice negativo desde el final
5
# Verificar membresía
iex> Enum.member?(lista, 3)
true
iex> 3 in lista # Alternativa más legible
true
map/2 - Transformar elementos
Aplica una función a cada elemento y retorna una nueva lista:
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
# Con sintaxis corta
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]
# Con captura de función
iex> Enum.map(["hola", "mundo"], &String.upcase/1)
["HOLA", "MUNDO"]
# Con mapas
iex> usuarios = [%{nombre: "Ana"}, %{nombre: "Pedro"}]
iex> Enum.map(usuarios, & &1.nombre)
["Ana", "Pedro"]
filter/2 - Filtrar elementos
Retorna solo los elementos que cumplen una condición:
iex> Enum.filter([1, 2, 3, 4, 5], fn x -> x > 2 end)
[3, 4, 5]
# Filtrar pares
iex> Enum.filter(1..10, &(rem(&1, 2) == 0))
[2, 4, 6, 8, 10]
# reject es lo opuesto
iex> Enum.reject(1..10, &(rem(&1, 2) == 0))
[1, 3, 5, 7, 9]
reduce/3 - Acumular valores
Reduce una colección a un solo valor:
# Sumar todos los elementos
iex> Enum.reduce([1, 2, 3], 0, fn x, acc -> x + acc end)
6
# Sintaxis corta
iex> Enum.reduce([1, 2, 3], 0, &(&1 + &2))
6
# También existe Enum.sum/1
iex> Enum.sum([1, 2, 3])
6
# Construir un mapa a partir de una lista
iex> palabras = ["hola", "mundo", "elixir"]
iex> Enum.reduce(palabras, %{}, fn palabra, acc ->
...> Map.put(acc, palabra, String.length(palabra))
...> end)
%{"elixir" => 6, "hola" => 4, "mundo" => 5}
Más funciones útiles de Enum
Ordenamiento
iex> Enum.sort([3, 1, 2])
[1, 2, 3]
# Orden descendente
iex> Enum.sort([3, 1, 2], :desc)
[3, 2, 1]
# Por criterio personalizado
iex> usuarios = [%{nombre: "Zara"}, %{nombre: "Ana"}]
iex> Enum.sort_by(usuarios, & &1.nombre)
[%{nombre: "Ana"}, %{nombre: "Zara"}]
Búsqueda
# Encontrar el primer elemento que cumple condición
iex> Enum.find([1, 2, 3, 4], &(&1 > 2))
3
# Verificar si alguno cumple
iex> Enum.any?([1, 2, 3], &(&1 > 2))
true
# Verificar si todos cumplen
iex> Enum.all?([2, 4, 6], &(rem(&1, 2) == 0))
true
Agrupación y partición
# Agrupar por criterio
iex> Enum.group_by(["ana", "bob", "amy"], &String.first/1)
%{"a" => ["ana", "amy"], "b" => ["bob"]}
# Partir en dos grupos
iex> Enum.split_with(1..10, &(rem(&1, 2) == 0))
{[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]}
Tomar y soltar
iex> Enum.take([1, 2, 3, 4, 5], 3)
[1, 2, 3]
iex> Enum.drop([1, 2, 3, 4, 5], 2)
[3, 4, 5]
iex> Enum.take_while([1, 2, 3, 4], &(&1 < 3))
[1, 2]
iex> Enum.chunk_every([1, 2, 3, 4, 5], 2)
[[1, 2], [3, 4], [5]]
Rangos
Los rangos son una forma eficiente de representar secuencias:
iex> 1..5
1..5
iex> Enum.to_list(1..5)
[1, 2, 3, 4, 5]
# Con paso
iex> Enum.to_list(1..10//2)
[1, 3, 5, 7, 9]
# Descendente
iex> Enum.to_list(5..1//-1)
[5, 4, 3, 2, 1]
Comprehensions (for)
Las comprehensions son una forma elegante de transformar y filtrar colecciones:
# Básico: equivale a Enum.map
iex> for x <- [1, 2, 3], do: x * 2
[2, 4, 6]
# Con filtro
iex> for x <- 1..10, rem(x, 2) == 0, do: x
[2, 4, 6, 8, 10]
# Múltiples generadores (producto cartesiano)
iex> for x <- [1, 2], y <- [:a, :b], do: {x, y}
[{1, :a}, {1, :b}, {2, :a}, {2, :b}]
# Con pattern matching
iex> usuarios = [%{nombre: "Ana", activo: true}, %{nombre: "Pedro", activo: false}]
iex> for %{nombre: nombre, activo: true} <- usuarios, do: nombre
["Ana"]
into: Cambiar el tipo de resultado
# Generar un mapa
iex> for x <- ["a", "b"], into: %{}, do: {x, String.upcase(x)}
%{"a" => "A", "b" => "B"}
# Generar un string
iex> for c <- [72, 111, 108, 97], into: "", do: <<c>>
"Hola"
El módulo Stream
Stream es como Enum pero perezoso (lazy). Las operaciones no se ejecutan hasta que son necesarias:
# Enum ejecuta cada paso completamente
1..1000000
|> Enum.map(&(&1 * 2)) # Crea lista de 1M elementos
|> Enum.filter(&(&1 < 10)) # Filtra toda la lista
|> Enum.take(3) # Toma 3
# Stream procesa elemento por elemento
1..1000000
|> Stream.map(&(&1 * 2)) # No ejecuta aún
|> Stream.filter(&(&1 < 10)) # No ejecuta aún
|> Enum.take(3) # Aquí se ejecuta todo (solo 3 elementos)
Usa Stream cuando trabajes con colecciones grandes o infinitas, o cuando encadenes muchas operaciones. Para colecciones pequeñas, Enum es más simple y a menudo más rápido.
Streams infinitos
# Secuencia infinita de números
iex> Stream.iterate(0, &(&1 + 1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
# Repetir un valor
iex> Stream.repeatedly(fn -> :rand.uniform(10) end) |> Enum.take(5)
[7, 2, 9, 1, 4]
# Ciclar una lista
iex> Stream.cycle([:a, :b, :c]) |> Enum.take(7)
[:a, :b, :c, :a, :b, :c, :a]
Ejemplo práctico: Procesamiento de datos
defmodule Ventas do
def procesar(transacciones) do
transacciones
|> Enum.filter(& &1.completada) # Solo completadas
|> Enum.map(& &1.monto) # Extraer montos
|> Enum.filter(&(&1 > 0)) # Solo positivos
|> estadisticas()
end
defp estadisticas(montos) do
%{
total: Enum.sum(montos),
promedio: Enum.sum(montos) / max(Enum.count(montos), 1),
maximo: Enum.max(montos, fn -> 0 end),
cantidad: Enum.count(montos)
}
end
end
Dada una lista de palabras, usa Enum para:
- Filtrar las que tienen más de 5 letras
- Convertirlas a mayúsculas
- Ordenarlas por longitud (de menor a mayor)
Implementa una función que tome una lista de elementos y retorne un mapa con la frecuencia de cada uno:
frecuencias([:a, :b, :a, :c, :a])
# => %{a: 3, b: 1, c: 1}
Usa una comprehension para generar una tabla de multiplicar del 1 al 10 como lista de tuplas {a, b, resultado}.