I\'m using a HashMap
to count the occurrences of different characters in a string:
let text = \"GATTACA\";
let mut counts: HashMap
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).
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
}
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];
.