More concise HashMap initialization
You can use iterators to emulate the dictionary comprehension, e.g.
let counts = "ACGT".chars().map(|c| (c, 0_i32)).collect::<HashMap<_, _>>();
or even for c in "ACGT".chars() { counts.insert(c, 0) }
.
Also, one can write a macro to allow for concise initialisation of arbitrary values.
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
used like let counts = hashmap!['A' => 0, 'C' => 0, 'G' => 0, 'T' => 0];
.
Another way that I see in the official documentation:
use std::collections::HashMap;
fn main() {
let timber_resources: HashMap<&str, i32> =
[("Norway", 100),
("Denmark", 50),
("Iceland", 10)]
.iter().cloned().collect();
// use the values stored in map
}
EDIT
When I visit the official doc again, I see that the sample is updated (and the old sample is removed). So here is the latest solution with Rust 1.56:
let vikings = HashMap::from([
("Norway", 25),
("Denmark", 24),
("Iceland", 12),
]);
Starting with Rust 1.56, you can use from()
to build a Hashmap
from an array of key-value pairs. This makes it possible to initialize concisely without needing to specify types or write macros.
use std::collections::HashMap;
fn main() {
let m = HashMap::from([
('A', 0),
('C', 0),
('G', 0),
('T', 0)
]);
}
This (very common) scenario is why I heard angels singing when I discovered Python's defaultdict, a dictionary which, if you try to get a key that isn't in the dictionary, immediately creates a default value for that key with a constructor you supply when you declare the defaultdict. So, in Python, you can do things like:
counts = defaultdict(lambda: 0)
counts['A'] = counts['A'] + 1
For counting occurrences, this is the favored approach since trying to pre-populate the hashtable becomes problematic when the keyspace is either large or unknown to the programmer (Imagine something which counts words in text you feed to it. Are you going to pre-populate with all English words? What if a new word enters the lexicon?).
You can achieve this same thing in Rust with the lesser-known methods in the Option class. Seriously, when you have some free time, just read through all of the methods in Option. There are some very handy methods in there.
Although not dealing with concise initialization (which is what the wubject is asking for) here are two answers (which are, arguably, better for doing what OP is trying to do).
let text = "GATTACA";
let mut counts:HashMap<char,i32> = HashMap::new();
for c in text.chars() {
counts.insert(c,*(counts.get(&c).get_or_insert(&0))+1);
}
The above method uses Option's get or insert() method which, if it's a Some(), returns the value and, if a None, returns a value you provide. Note that, even though the method is named get_or_insert(), it is not inserting into the hashmap; this is a method for Option and the hashmap has no idea this fail-over is taking place. The nice bit is that this unwraps the value for you. This is pretty similar to Python's defaultdict, with the difference that you have to provide a default value in multiple locations in your code (inviting bugs, but also providing an added flexibility that defaultdict lacks).
let text = "GATTACA";
let mut counts:HashMap<char,i32> = HashMap::new();
for c in text.chars() {
counts.insert(c,counts.get(&c).or_else(|| Some(&0)).unwrap()+1);
}
This approach uses Option's or else() method which lets you specify a lambda for producing the value and, crucially, lets you still return a None (imagine if you wanted to check a hashmap for a key and, if not found, check another hashmap for it, and, only if not found in either, did you produce a None). Because or else() returns an option, we must use unwrap() (which would panic if used on a None, but we know that won't apply here).