7

Is there any way to insert multiple entries into a HashMap at once in Rust? Or to initialize it with multiple entries? Anything other than manually calling insert on every single element you're inserting?

Edit for an example using English letter frequencies:

I basically want:

let frequencies = { 
  'a': 0.08167, 
  'b': 0.01492, 
  ... 
  'z': 0.00074 
}

I know I can achieve the same result by doing a for loop like the following, but I want to know if there is a way to do this without creating additional arrays and then looping over them, or a more elegant solution in general.

let mut frequencies = HashMap::new();
let letters = ['a','b','c', ...... 'z'];
let freqs = [0.08167, 0.01492, 0.02782, ......., 0.00074];
for i in 0..26 {
    frequencies.insert(letters[i], freqs[i]);
}

For a literal, I could use the answer here, which will probably work fine for this example, but I'm curious whether there's a way to do this without it being a literal, in case this comes up in the future.

firechant
  • 886
  • 1
  • 14
  • 22
  • 1
    Possible duplicate of [How do I create a HashMap literal?](https://stackoverflow.com/questions/27582739/how-do-i-create-a-hashmap-literal) – JAL Jun 15 '17 at 19:24
  • 4
    Can you explain why you want this? Are you trying to avoid typing "insert" a bunch of times? (If so there is a Hashmap literal macro crate.) Are you trying to avoid resizing the Hashmap? (If so then you can initialise it with 'with_capacity') Are you trying to avoid round trips through the runtime? (If so I'd tell you not to worry about it, that doesn't really exist like it would in Python or JavaScript.) Are you trying to shorten the code required to merge two Hashmaps because you do it in a lot of places? – david king Jun 15 '17 at 19:33
  • *other than manually calling insert on every single element* — yes, you can use a loop and then you "manually" only call `insert` once. – Shepmaster Jun 16 '17 at 12:55
  • The purpose is to initialize the HashMap with some values. As an example, English letter frequencies. I basically want { 'a': 0.08167, 'b': 0.01492, ... , 'z': 0.00074 }. I suppose I could create an array of keys, an array of values, and then loop over the indices to insert the elements like `map.insert(letter[i], freq[i])`. I was just wondering if there's a way do this all at once. I'll edit my question to reflect this. – firechant Jun 16 '17 at 18:50
  • There's no real reason that the loop solution is bad. I had been thinking about it from a Python-y way, trying to avoid runtime issues, but that's not as much of a problem here. It just feels less elegant, I suppose. But alas. – firechant Jun 16 '17 at 19:02

2 Answers2

17

Is there any way to insert multiple entries into a HashMap at once in Rust?

Yes, you can extend a HashMap with values from an Iterator, like this:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.extend((1..3).map(|n| (format!("{}*2=", n), n * 2)));
    map.extend((7..9).map(|n| (format!("{}*2=", n), n * 2)));
    println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}

It is even a bit faster than calling the insert manually, because extend uses the size hint provided by the Iterator in order to reserve some space beforehand.

Check out the source code of the method here, in map.rs.

Or to initialize it with multiple entries?

This is possible as well, thanks to HashMap implementing the FromIterator trait. When a collection implements FromIterator, you can use the Iterator::collect shorthand to construct it. Consider the following examples, all of them generating the same map:

use std::collections::HashMap;

fn main() {
    let mut map: HashMap<_, _> = (1..3).map(|n| (format!("{}*2=", n), n * 2)).collect();
    map.extend((7..9).map(|n| (format!("{}*2=", n), n * 2)));
    println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}

use std::collections::HashMap;

fn main() {
    let map: HashMap<_, _> = (1..3)
        .chain(7..9)
        .map(|n| (format!("{}*2=", n), n * 2))
        .collect();
    println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}

use std::collections::HashMap;
use std::iter::FromIterator;

fn main() {
    let iter = (1..3).chain(7..9).map(|n| (format!("{}*2=", n), n * 2));
    let map = HashMap::<String, u32>::from_iter(iter);
    println!("{:?}", map); // Prints {"1*2=": 2, "8*2=": 16, "7*2=": 14, "2*2=": 4}.
}
ArtemGr
  • 11,684
  • 3
  • 52
  • 85
  • What if the value type of the map is a `Vec`, and I want the extend method extends the inside Vec? I have tried but find that the Vec value is replaced, it there any solution for my use cases? – prehistoricpenguin Apr 19 '21 at 03:10
0
use std::collections::HashMap;

fn main() {
    let pairs = [
        ("a", 1),
        ("b", 2),
        ("c", 3),
        ("z", 50),
    ];

    println!("1. Insert multiple entries into a HashMap at once");
    let mut map = HashMap::new();
    map.extend(pairs);
    println!("map: {map:#?}\n");
    
    println!("2. Initialize with multiple entries");
    let map = HashMap::from([
        ("a", 1),
        ("b", 2),
        ("c", 3),
        ("z", 50),
    ]);
    println!("map: {map:#?}\n");

    println!("3. Initialize with multiple entries");
    let map = HashMap::from(pairs);
    println!("map: {map:#?}\n");
    
    println!("4. Initialize with multiple entries");
    let map: HashMap<_, _> = pairs.into();
    println!("map: {map:#?}");
}

See the Rust Playground.

Claudio Fsr
  • 106
  • 6