How to lookup from and insert into a HashMap efficiently?
Solution 1
The entry
API is designed for this. In manual form, it might look like
let values = match map.entry(key) {
Entry::Occupied(o) => o.into_mut(),
Entry::Vacant(v) => v.insert(default),
};
One can use the briefer form via Entry::or_insert_with
:
let values = map.entry(key).or_insert_with(|| default);
If default
is already computed, or if it's OK/cheap to compute even when it isn't inserted, you can use Entry::or_insert
:
let values = map.entry(key).or_insert(default);
If the HashMap
's value implements Default
, you can use Entry::or_default
, although you may need to provide some type hints:
let values = map.entry(key).or_default();
Solution 2
I've used huon's answer and implemented it as a trait:
use std::collections::HashMap;
use std::hash::Hash;
pub trait InsertOrGet<K: Eq + Hash, V: Default> {
fn insert_or_get(&mut self, item: K) -> &mut V;
}
impl<K: Eq + Hash, V: Default> InsertOrGet<K, V> for HashMap<K, V> {
fn insert_or_get(&mut self, item: K) -> &mut V {
return match self.entry(item) {
std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
std::collections::hash_map::Entry::Vacant(v) => v.insert(V::default()),
};
}
}
Then I can do:
use crate::utils::hashmap::InsertOrGet;
let new_or_existing_value: &mut ValueType = my_map.insert_or_get(my_key.clone());
Related videos on Youtube
Comments
-
Yusuke Shinyama about 2 years
I'd like to do the following:
- Lookup a
Vec
for a certain key, and store it for later use. - If it doesn't exist, create an empty
Vec
for the key, but still keep it in the variable.
How to do this efficiently? Naturally I thought I could use
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 } };
When I tried it, it gave me errors like:
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
I ended up with doing something like this, but I don't like the fact that it performs the lookup twice (
map.contains_key
andmap.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!"); } };
Is there a safe way to do this with just one
match
? - Lookup a
-
Yusuke Shinyama over 9 yearsThanks for a swift anser! Now I've learned I should look into a little deep down to the documents.
-
Pascalius over 6 yearsthe issue with entry() is, that you always have to clone the key, is there a way to avoid this?
-
kbolino almost 6 years@Pascalius you could make your key type
&T
(if the keys outlive the map, e.g. static strings) orRc<T>
instead ofT
-- but it's not pretty in either case -
Chris Beck over 5 years@Pascalius: you can use
v.key()
in the expression fordefault
, and then it will get a reference to the key as it exists in the hashmap, so you may avoid a clone this way