rust-iteradores-map-filter-collect

Map, filter y collect en Rust: iteradores funcionales

  • 4 min

Los adaptadores y consumidores de iteradores son métodos que transforman una secuencia o fuerzan su evaluación.

En el artículo anterior dijimos que los iteradores son perezosos. Son como una cadena de montaje detenida. Hoy vamos a aprender a:

  1. Modificar la cadena de montaje (añadir operaciones que transforman o descartan piezas) con adaptadores.
  2. Encender el interruptor para que la cadena produzca un resultado mediante un consumidor.

Esta forma de programar, encadenando operaciones pequeñas, combina una API fluida con un estilo funcional.

Adaptadores: transformar la secuencia

Los adaptadores son métodos que toman un iterador y devuelven otro iterador diferente con alguna lógica añadida. Recuerda: aún no se ejecuta nada, solo estamos configurando la tubería (pipeline).

map: transformación uno a uno

Toma cada elemento y le aplica un closure para transformarlo en otra cosa.

let numeros = vec![1, 2, 3];

// Crea un iterador que multiplicará por 2 cuando se le pida.
// El tipo del iterador cambia de Iter<i32> a Map<Iter<i32>, Closure>
let dobles = numeros.iter().map(|x| x * 2);
Copied!

filter: selección

Toma cada elemento y decide si pasa o no, basándose en un closure que devuelve bool (predicado).

let numeros = vec![1, 2, 3, 4, 5];

// Ojo: filter recibe una REFERENCIA al elemento que itera.
// Si iter() ya da referencias (&i32), filter recibe (&&i32).
// Por eso usamos **x o desestructuramos con |&x|
let pares = numeros.iter().filter(|&x| x % 2 == 0);
Copied!

Otros adaptadores útiles

  • take(n): Toma solo los primeros n elementos y descarta el resto.
  • skip(n): Salta los primeros n elementos.
  • zip(otro_iter): Combina dos iteradores en uno solo que devuelve tuplas (a, b).
  • enumerate(): Devuelve tuplas (índice, valor).
let caracteres = vec!['a', 'b', 'c'];

for (i, c) in caracteres.iter().enumerate() {
    println!("En la posición {} está la letra {}", i, c);
}
Copied!

Consumidores: materializar el resultado

Para que todo lo anterior sirva de algo, necesitamos un método que consuma el iterador y genere un valor final.

collect: el recolector universal

Es el consumidor más importante. Transforma el iterador en una Colección (como Vec, String, HashMap, etc.).

El problema es que collect es tan genérico que Rust a menudo no sabe qué quieres crear. ¿Quieres un Vec<i32>? ¿Un HashSet<i32>? ¿Una LinkedList?

Por eso, casi siempre necesitamos ayudar al compilador. Tenemos dos formas:

Anotación de tipo en la variable:

let dobles: Vec<i32> = numeros.iter().map(|x| x * 2).collect();
Copied!

Sintaxis turbofish (::<>): Es un nombre divertido para una sintaxis curiosa. Le decimos a collect el tipo explícito.

let dobles = numeros.iter().map(|x| x * 2).collect::<Vec<i32>>();
Copied!

Otros consumidores comunes

  • sum(): Suma todos los elementos (debe ser un tipo numérico).
  • find(predicado): Devuelve el primer elemento que cumple la condición (Option<T>). Cortocircuita (deja de iterar en cuanto lo encuentra).
  • any() / all(): Devuelven bool si alguno o todos cumplen la condición.

Encadenar operaciones

Ahora todo encaja. Podemos combinar estas piezas para realizar operaciones complejas de forma muy legible.

Ejemplo: Imagina que tenemos una lista de números. Queremos:

  1. Quedarnos solo con los mayores de 10.
  2. Multiplicarlos por 3.
  3. Sumar el resultado total.

Estilo imperativo con un bucle:

let numeros = vec![5, 12, 3, 20, 8];
let mut suma = 0;
for n in &numeros {
    if *n > 10 {
        let valor = n * 3;
        suma += valor;
    }
}
println!("Resultado: {}", suma);
Copied!

Estilo funcional con iteradores:

let numeros = vec![5, 12, 3, 20, 8];

let suma: i32 = numeros.iter()
    .filter(|&n| *n > 10) // 1. Filtramos
    .map(|n| n * 3)       // 2. Transformamos
    .sum();               // 3. Consumimos

println!("Resultado: {}", suma);
Copied!

El código funcional describe QUÉ queremos hacer, no CÓMO hacerlo paso a paso. Es más fácil de leer, más difícil de equivocar y, gracias a las optimizaciones de Rust, igual de rápido.