Comment rechercher et insérer efficacement dans un HashMap?

102

J'aimerais faire ce qui suit:

  • Recherchez une Vecclé spécifique et stockez-la pour une utilisation ultérieure.
  • S'il n'existe pas, créez un vide Vecpour la clé, mais gardez-le toujours dans la variable.

Comment faire cela efficacement? Naturellement, j'ai pensé que je pourrais utiliser match:

use std::collections::HashMap;

// This code doesn't compile.
let mut map = HashMap::new();
let key = "foo";
let values: &Vec<isize> = match map.get(key) {
    Some(v) => v,
    None => {
        let default: Vec<isize> = Vec::new();
        map.insert(key, default);
        &default
    }
};

Quand je l'ai essayé, cela m'a donné des erreurs comme:

error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
  --> src/main.rs:11:13
   |
7  |     let values: &Vec<isize> = match map.get(key) {
   |                                     --- immutable borrow occurs here
...
11 |             map.insert(key, default);
   |             ^^^ mutable borrow occurs here
...
15 | }
   | - immutable borrow ends here

J'ai fini par faire quelque chose comme ça, mais je n'aime pas le fait qu'il effectue la recherche deux fois ( map.contains_keyet map.get):

// This code does compile.
let mut map = HashMap::new();
let key = "foo";
if !map.contains_key(key) {
    let default: Vec<isize> = Vec::new();
    map.insert(key, default);
}
let values: &Vec<isize> = match map.get(key) {
    Some(v) => v,
    None => {
        panic!("impossiburu!");
    }
};

Existe-t-il un moyen sûr de le faire avec un seul match?

Yusuke Shinyama
la source

Réponses:

120

L' entryAPI est conçue pour cela. Sous forme manuelle, cela pourrait ressembler à

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

let values: &Vec<isize> = match map.entry(key) {
    Entry::Occupied(o) => o.into_mut(),
    Entry::Vacant(v) => v.insert(default)
};

Ou on peut utiliser le formulaire plus bref:

map.entry(key).or_insert_with(|| default)

Si le defaultcalcul est OK / bon marché même s'il n'est pas inséré, cela peut aussi être simplement:

map.entry(key).or_insert(default)
huon
la source
Merci pour une réponse rapide! Maintenant, j'ai appris que je devrais examiner un peu plus en profondeur les documents.
Yusuke Shinyama
22
le problème avec entry () est que vous devez toujours cloner la clé, y a-t-il un moyen d'éviter cela?
Pascalius
@Pascalius vous pouvez créer votre type de clé &T(si les clés survivent à la carte, par exemple des chaînes statiques) ou Rc<T>au lieu de T- mais ce n'est pas joli dans les deux cas
kbolino
@Pascalius: vous pouvez utiliser v.key()dans l'expression for default, puis il obtiendra une référence à la clé telle qu'elle existe dans la hashmap, vous pouvez donc éviter un clone de cette façon
Chris Beck