Contar datos con HashMap

RustBeginner
Practicar Ahora

Introducción

Un vector organiza los valores según una posición numérica. En cambio, HashMap<K, V> asocia cada clave única de tipo K con un valor de tipo V, por lo que resulta útil para contar elementos, almacenar configuraciones y gestionar otros datos indexados por clave.

Creará un mapa pequeño para contar palabras, recuperará y reemplazará un recuento, utilizará la API de entradas para palabras existentes y ausentes, y recorrerá todos los valores sin depender del orden de las claves, que no está especificado.

Insertar y recuperar valores asociados a claves

En este paso, insertará dos pares clave-valor y recuperará de forma segura uno de los recuentos.

Entre en el proyecto y abra el código fuente:

cd /home/labex/project/word-counts
nano src/main.rs

La ruta preparada std::collections::HashMap identifica HashMap dentro del módulo de colecciones de la biblioteca estándar de Rust. La línea use asigna a esa ruta larga el nombre local abreviado HashMap. Por ahora, considérelo código de importación ya preparado; las rutas y las importaciones se explican por completo en los módulos posteriores de Lab. HashMap<String, u32> almacena claves de texto propias y recuentos de enteros sin signo. Reemplace el comentario Step 1a por:

    let _ = counts.insert(String::from("rust"), 1);
    let _ = counts.insert(String::from("safe"), 1);

insert devuelve cualquier valor que estuviera almacenado previamente para esa clave. Como estas claves son nuevas, el resultado es None. El patrón let _ = ...; evalúa la operación y descarta deliberadamente su resultado. Este _ independiente es distinto de un nombre como _returned_id: el comodín independiente no crea una vinculación que pueda utilizar más adelante.

Reemplace el comentario Step 1b por:

    match counts.get("rust") {
        Some(count) => println!("Rust count: {count}"),
        None => println!("Rust count: missing"),
    }

get acepta el texto prestado "rust" y devuelve un Option<&u32> sin extraer la clave ni el recuento del mapa. Guarde los cambios y salga de nano; después, compruebe y ejecute el programa:

cargo check
cargo run --quiet
Unique words: 2
Rust count: 1

La primera línea demuestra que las claves son únicas; la segunda confirma que la búsqueda encontró el recuento almacenado.

Reemplazar un recuento existente

En este paso, utilizará insert con una clave existente y comprobará el valor que se reemplazó.

Abra el código fuente:

nano src/main.rs

Reemplace el comentario Step 2 por:

    let previous = counts.insert(String::from("rust"), 2);
    println!("Previous rust count: {previous:?}");

Un mapa conserva un solo valor por cada clave. El nuevo valor 2 reemplaza a 1, y el Some(1) devuelto permite observar ese reemplazo. Guarde los cambios y salga de nano; después, ejecute:

cargo run --quiet

La nueva línea debe ser:

Previous rust count: Some(1)

El número de palabras únicas sigue siendo dos, porque reemplazar un valor no crea otra clave.

Actualizar recuentos con la API de entradas

En este paso, incrementará una palabra existente y una palabra ausente mediante la misma API.

entry(key).or_insert(default) devuelve una referencia mutable al valor existente de la clave, o inserta el valor predeterminado y devuelve una referencia mutable a ese valor. Abra el código fuente:

nano src/main.rs

Reemplace el comentario Step 3 por:

    *counts.entry(String::from("safe")).or_insert(0) += 1;
    *counts.entry(String::from("fast")).or_insert(0) += 1;
    println!("Safe count: {}", counts["safe"]);
    println!("Fast count: {}", counts["fast"]);

Analice la actualización compacta desde dentro hacia fuera. Primero, entry(key) selecciona la ubicación asociada con la clave, tanto si está ocupada como si está vacía. Después, or_insert(0) garantiza que exista un valor y devuelve una referencia mutable, &mut u32, a ese valor.

El * inicial permite acceder al u32 al que apunta esa referencia. Finalmente, += 1 suma uno y guarda el nuevo recuento en el mapa.

Por lo tanto, safe pasa a ser 2, mientras que fast, que no existía, comienza en cero y pasa a ser 1. Los corchetes también permiten leer un valor mediante su clave. La indexación del mapa provocaría un pánico si faltara la clave; aquí es segura únicamente porque las dos llamadas anteriores a entry garantizan que ambas claves existen. Siga utilizando get cuando la clave pueda estar ausente.

Guarde los cambios y salga de nano; después, ejecute:

cargo run --quiet

Las últimas líneas deben ser:

Safe count: 2
Fast count: 1

Una sola operación gestionó tanto el caso de una clave ocupada como el de una clave vacía.

Recorrer el mapa sin depender del orden de las claves

En este paso, recorrerá todos los recuentos almacenados y calculará un total independiente del orden.

Los mapas hash no garantizan un orden estable al recorrer sus claves, por lo que una verificación adecuada para principiantes no debe esperar que las claves se muestren en una secuencia concreta. El método values() visita todos los valores almacenados, lo cual basta para calcular un total.

Abra el código fuente y reemplace el comentario Step 4:

nano src/main.rs
    let mut total = 0;
    for count in counts.values() {
        total += count;
    }
    println!("Total occurrences: {total}");

Aquí count es una referencia compartida, y += puede sumar el entero al que hace referencia. Guarde los cambios y salga de nano; después, compruebe y ejecute el programa:

cargo check
cargo run --quiet

La última línea debe ser:

Total occurrences: 5

El total combina rust = 2, safe = 2 y fast = 1 sin depender del orden en que se recorren.

Resumen

Insertó, recuperó, reemplazó e incrementó recuentos asociados a claves. Después, recorrió todos los valores del mapa sin suponer que las claves mantienen un orden estable.