HashMap::entry în Rust

Caută o cheie o singură dată și îți dă „locul” ei (Entry), ca să inserezi, să citești sau să modifici fără o a doua căutare.

Sintaxă
pub fn entry(&mut self, key: K) -> Entry<'_, K, V>

pub enum Entry<'a, K, V> {
    Occupied(OccupiedEntry<'a, K, V>),
    Vacant(VacantEntry<'a, K, V>),
}

Ce face HashMap::entry

Foarte des vrei: „dacă cheia există, modific valoarea; dacă nu, o inserez”. Fără entry, scrii if let Some(n) = m.get_mut(k) { *n += 1 } else { m.insert(k, 1); }: două căutări și cod lung. m.entry(k) caută cheia o singură dată și returnează un Entry, adică locul cheii în dicționar, ocupat sau liber.

Entry este un enum cu două variante: Occupied (cheia există, ai acces la valoarea ei) și Vacant (cheia lipsește, poți insera). De cele mai multe ori nu faci match pe el, ci apelezi direct o metodă: or_insert(v), or_insert_with(|| ...), or_default() (inserează dacă lipsește și îți dau &mut V) sau and_modify(|v| ...) (modifică dacă există). Cea mai cunoscută formă: *m.entry(cuvant).or_insert(0) += 1;.

entry primește cheia prin valoare (de exemplu un String), pentru că s-ar putea să trebuiască inserată. Cât timp ții un Entry sau referința primită de la el, dicționarul este împrumutat mutabil. BTreeMap are exact același API (btree_map::Entry), deci tot ce e pe această pagină merge și acolo.

Parametri

Parametru Descriere
key Cheia căutată, dată prin valoare (este mutată în dicționar doar dacă se inserează).

Ce returnează

Un Entry<'_, K, V>: Entry::Occupied dacă cheia există, altfel Entry::Vacant.

Exemple

Exemplul 1: Frecvența cuvintelor dintr-un text

Împărțim textul la orice caracter care nu e literă sau cifră și numărăm fiecare cuvânt, cu litere mici. or_insert(0) pune 0 la prima apariție și returnează &mut u32, pe care îl mărim. La final sortăm după frecvență, apoi alfabetic.

use std::collections::HashMap;

fn main() {
    let text = "Ana are mere. Mihai are pere, iar Ana are și prune. \
                Mere, pere, prune: Ana le duce pe toate la piață, la Iași.";

    let mut frecventa: HashMap<String, u32> = HashMap::new();
    for cuvant in text.split(|c: char| !c.is_alphanumeric()) {
        if cuvant.is_empty() {
            continue;
        }
        *frecventa.entry(cuvant.to_lowercase()).or_insert(0) += 1;
    }

    let mut lista: Vec<(&String, &u32)> = frecventa.iter().collect();
    lista.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0)));
    for (cuvant, n) in lista.iter().take(6) {
        println!("{n} × {cuvant}");
    }
    println!("{} cuvinte distincte", frecventa.len());
}

Rezultat

3 × ana
3 × are
2 × la
2 × mere
2 × pere
2 × prune
15 cuvinte distincte

Exemplul 2: match pe Entry: Occupied sau Vacant

Când comportamentul diferă mult între cele două cazuri, faci match. VacantEntry::insert pune valoarea; OccupiedEntry::get o citește.

use std::collections::HashMap;
use std::collections::hash_map::Entry;

fn main() {
    let mut inscrieri: HashMap<&str, u32> = HashMap::new();
    for (nume, varsta) in [("Ana", 17), ("Mihai", 18), ("Ana", 19)] {
        match inscrieri.entry(nume) {
            Entry::Vacant(loc) => {
                loc.insert(varsta);
                println!("{nume} a fost înscris(ă), {varsta} ani");
            }
            Entry::Occupied(existent) => {
                println!("{nume} este deja înscris(ă), cu vârsta {}", existent.get());
            }
        }
    }
    println!("{} persoane înscrise", inscrieri.len());
}

Rezultat

Ana a fost înscris(ă), 17 ani
Mihai a fost înscris(ă), 18 ani
Ana este deja înscris(ă), cu vârsta 17
2 persoane înscrise

Exemplul 3: Elevi grupați pe orașe

or_default() creează un Vec gol pentru orașul nou și returnează &mut Vec, în care adăugăm direct.

use std::collections::HashMap;

fn main() {
    let elevi = [("Ana", "Iași"), ("Mihai", "Cluj"), ("Ioana", "Iași"), ("Radu", "Brașov"), ("Elena", "Cluj")];
    let mut pe_orase: HashMap<&str, Vec<&str>> = HashMap::new();
    for (nume, oras) in elevi {
        pe_orase.entry(oras).or_default().push(nume);
    }

    let mut orase: Vec<_> = pe_orase.keys().copied().collect();
    orase.sort();
    for o in orase {
        println!("{o}: {}", pe_orase[o].join(", "));
    }
}

Rezultat

Brașov: Radu
Cluj: Mihai, Elena
Iași: Ana, Ioana

Greșeli frecvente

  • Scrii m.entry(&cuvant) pentru un HashMap<String, _>: entry vrea cheia prin valoare (expected `String`, found `&String`). Dă cuvant.to_string() sau cuvant.clone(); pentru chei &str, dă direct &str-ul.
  • Uiți steluța: m.entry(k).or_insert(0) += 1 nu compilează, pentru că or_insert returnează &mut V. Scrie *m.entry(k).or_insert(0) += 1.
  • Păstrezi referința primită de la or_insert și apoi folosești dicționarul: cannot borrow `m` as immutable because it is also borrowed as mutable. Termină lucrul cu referința înainte.
  • Faci contains_key + insert, sau get_mut + insert: două căutări și cod mai lung. entry le înlocuiește pe amândouă.

Comenzi înrudite

  • Entry::or_insert: Dacă cheia lipsește, inserează valoarea dată; în ambele cazuri returnează o referință mutabilă la valoarea din dicționar.
  • Entry::or_insert_with: Ca or_insert, dar valoarea de inserat e calculată de un closure, apelat doar dacă cheia lipsește.
  • Entry::or_default: Dacă cheia lipsește, inserează valoarea implicită a tipului (0, vector gol, text gol) și returnează &mut V.
  • Entry::and_modify: Dacă cheia există, aplică funcția dată pe valoarea ei; returnează Entry-ul, ca să poți continua cu or_insert.
  • HashMap::insert: Pune o pereche cheie-valoare în dicționar; dacă cheia exista, înlocuiește valoarea și o returnează pe cea veche.
  • BTreeMap: Dicționar cu cheile mereu sortate: aceleași operații ca HashMap, dar parcurgerea și afișarea sunt în ordinea cheilor.