Search code examples
rusthashtable

Rust hashmap of vectors, minimal lookup operations


I have a hashmap in rust that is keyed to a hashset of strings

I want to implement a method that insert strings to an existing key, if it doesn't exist, created an empty entry.

I have the follow code snippet, I really like how there's only 1 lookup

let mut map: HashMap<&str, HashSet<&str>> = HashMap::new();

let park1 = "poneyland";
let horse = "foo";

map.entry(park1)
   .or_insert_with(|| HashSet::from([horse]))
   .insert(horse);

But I want to make HashSet maximum size of 25.

To do so, I tried the following

match map.entry(park1){
    Entry::Occupied(x) => {
        let tmp_entry = x.into_mut();
        if tmp_entry.len() < 25 {
            Ok(tmp_entry.insert(horse))
        }
        else{
            Err("maximum size reached")
        }
    }
    Entry::Vacant(y) => {
        let tmp_hash_set = HashSet::from([horse]);
        Err(y.insert(tmp_hash_set))
    }
    };
}
   

But I get the following error

22 |   match map.entry(park1){
   |   ---------------------- `match` arms have incompatible types
...
25 | /         if tmp_entry.len() < 64{
26 | |             Ok(tmp_entry.insert(horse))
27 | |         }
28 | |         else{
29 | |             Err("maximum size reached")
30 | |         }
   | |_________- this is found to be of type `Result<bool, &str>`
...
34 |           Err(y.insert(tmp_hash_set))
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability
   |
   = note: expected enum `Result<bool, &str>`
              found enum `Result<_, &mut HashSet<&str>>`

rust playground

Update, per @Ry comments, I did the following:

let current_size = map.len();

match map.entry(park1){
    Entry::Occupied(x) => {
        let tmp_entry = x.into_mut();
        if tmp_entry.len() < 64{
            Ok(tmp_entry.insert(horse))
        }
        else{
            Err("maximum horses reached in park")
        }
    }
    Entry::Vacant(y) => {
        if current_size == 9999 {
            Err("maximum number of parks reached")
        }
        else{
            let tmp_hash_set = HashSet::from([horse]);
            y.insert(tmp_hash_set);
            Ok(true)
        }
    }
};

The above works, I'm curious if there's a better way?


Solution

  • The above works, I'm curious if there's a better way?

    Minute improvements are possible though the gist seems sound:

    You can lift the conditions as match guards, which obviates some of the blocks (brackets) and makes the code more declarative:

        match map.entry(park1) {
            Entry::Occupied(x) if x.get().len() < 64 => 
                Ok(x.into_mut().insert(horse)),
            Entry::Occupied(_) => Err("maximum horses reached in park"),
            Entry::Vacant(_) if current_size == 9999 => 
                Err("maximum number of parks reached"),
            Entry::Vacant(y) => {
                let tmp_hash_set = HashSet::from([horse]);
                y.insert(tmp_hash_set);
                Ok(true)
            }
        };
    

    You could also reorganise to e.g. put both the error cases up top for clarity though that's more of a taste thing:

        match map.entry(park1) {
            Entry::Occupied(x) if x.get().len() >= 64 =>
                Err("maximum horses reached in park"),
            Entry::Vacant(_) if current_size == 9999 =>
                Err("maximum number of parks reached"),
            Entry::Occupied(x) =>
                Ok(x.into_mut().insert(horse)),
            Entry::Vacant(y) => {
                let tmp_hash_set = HashSet::from([horse]);
                y.insert(tmp_hash_set);
                Ok(true)
            }
        };
    

    The tmp_hash_set is not necessary:

                y.insert(HashSet::from([horse]));
                Ok(true)