Sling Academy
Home/Rust/Rust - Implementing multi-mapping patterns with HashMap<K, Vec<V>> or HashMap<K, HashSet<V>>

Rust - Implementing multi-mapping patterns with HashMap> or HashMap>

Last updated: January 04, 2025

In many applications, it's often necessary to associate a single key with multiple values. Java developers commonly use collections like HashMap to implement such multi-mapping patterns. However, the built-in HashMap only allows one value per key. To associate multiple values, we can use a collection of values (such as a Vec<V> or a HashSet<V>) as the value in the HashMap. Each approach provides distinct advantages based on the use case.

Using HashMap<K, Vec<V>>

The HashMap<K, Vec<V>> pattern is suitable when you need to maintain the order of values or allow duplicate values for a specific key. Here’s how you can implement and use this pattern:


use std::collections::HashMap;

fn main() {
    let mut map: HashMap<String, Vec<&String>> = HashMap::new();

    map.entry("key1".to_string()).or_insert(Vec::new()).push("value1".to_string());
    map.entry("key1".to_string()).or_insert(Vec::new()).push("value2".to_string());

    map.entry("key2".to_string()).or_insert(Vec::new()).push("value3".to_string());

    for (key, values) in &map {
        println!("{}: {:?}", key, values);
    }
 }

In the example above, we create a HashMap where each key corresponds to a Vec, holding multiple values. We use the entry API to ensure that we initialize the vector if it doesn't exist before pushing a new value.

Using HashMap<K, HashSet<V>>

The HashMap<K, HashSet<V>> pattern is more appropriate when you need to ensure that each value associated with a key is unique. Here’s an example implementation:


use std::collections::{HashMap, HashSet};

fn main() {
    let mut map: HashMap<String, HashSet<String>> = HashMap::new();

    map.entry("key1".to_string()).or_insert(HashSet::new()).insert("value1".to_string());
    map.entry("key1".to_string()).or_insert(HashSet::new()).insert("value1".to_string());
    map.entry("key1".to_string()).or_insert(HashSet::new()).insert("value2".to_string());

    for (key, values) in &map {
        println!("{}: {:?}", key, values);
    }
}

In this pattern, the HashSet automatically manages duplication by only storing unique values. The usage of entry in combination with insert ensures that each value is added only once per key.

Handling Entries

Regardless of whether you use a Vec or HashSet, working with entries in the map often uses similar patterns. By leveraging the entry API, we can easily follow these patterns using idiomatic Rust code, ensuring each value container is created only when necessary.

For developers targeting high performance and ease of use, these idioms are preferred for their clarity and efficiency. However, always ensure to understand the needs of your application regarding ordering and duplication of stored values before deciding which pattern to adopt.

Displaying and Managing Collections

For displaying entries stored in HashMap, iterating through each key-value pair using a loop and accessing each collection allows full visibility of your stored data:


fn display_map(map: &HashMap<K, Vec<V>>)
where
    K: std::fmt::Display,
    V: std::fmt::Debug,
{
    for (key, values) in map {
        println!("{}: {:?}", key, values);
    }
}

In handling multi-mapping patterns, it’s beneficial to develop helper functions like display_map to maintain organized and readable code, especially as the complexity of your application increases.

Conclusion

Choosing between Vec and HashSet patterns when implementing multi-mapping structures in a language like Rust is driven by the specific requirements for duplication and order of values. By deeply understanding these patterns, developers can design systems that are both efficient and suited to the exact needs of their software.

Next Article: Rust - Splitting and chunking large vectors for parallel processing with rayon

Previous Article: Using generics to write functions that accept any collection type in Rust

Series: Collections in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior