5 minutes
Common Collections (Vector and Strings) in Rust [Notes]
Chapter 8: Common Collections (Hash Maps)
In the previous blog, I shared my notes on strings and vectors in Rust, and in this post we’ll cover Hash Maps. I personally have found their use in competitive programming, a lot, but hopefully as we move on, we’ll see lots of use-cases in real-life problems.
Hash Maps
Hash Maps: HashMap<K, V>
- You can’t access using indices, but through keys.
- Hash Maps store data on heap.
- Hash Maps are homogenous (all keys must have same type, and all values must have same type).
- Use
std::collections::HashMap
to bringHashMap
to scope.
Creating a New Hash Map
First Way:
use std::collections::HashMap;
let mut scores = HashMap::new();
// Now insert key, val pair
scores.insert("Kush", 3);
scores.insert("Kushashwa", 10);
Second Way: In case you have keys and values stored in two different vectors, and want to generate a hashmap from them, use collect()
.
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// Types will be infered by Rust from the data in the vectors
let mut scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
Why HashMap<_, _>
? collect()
method can store values into different datastructures, and we need to specify the type of scores
. For the types of keys and values, Rust can infer the types itself - hence we specify _
.
I was curious what into_iter()
does, and here is a very interesting answer to into_iter()
vs iter()
on Stackoverflow. Someone mentioned this blog post in the comments, I’ve this as a TODO - but from the looks of it, it might be useful.
If you are curious what teams.into_iter().zip(initial_scores.into_iter())
do? Great, check this:
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let tuple_output = teams.into_iter().zip(initial_scores.into_iter());
for item in tuple_output {
println!("{}, {}" ,item.0, item.1);
}
}
Outputs:
Blue, 10
Yellow, 50
So clearly, creating a vector of tuples (of a String
and Integer
). Maybe try passing a vector of different length to .zip()
function? ;)
Hash Maps and Ownership
Ownership is always the center of discussion when it comes to Rust. When you create a hashmap, and insert String
objects, i32
values, the ownership behaves differently:
- For types that implement
Copy
trait, likei32
, the values will be copied to the hashmap. - For
String
values (owned values), the values are moved into the hashmap and the hashmap will be the owner.
See the following example and corresponding error:
use std::collections::HashMap;
fn main() {
let str1 = String::from("Kush");
let str2 = String::from("Name");
let mut map = HashMap::new();
map.insert(str1, str2);
println!("{}", str1);
}
|
4 | let str1 = String::from("Kush");
| ---- move occurs because `str1` has type `String`, which does not implement the `Copy` trait
...
8 | map.insert(str1, str2);
| ---- value moved here
9 |
10 | println!("{}", str1);
| ^^^^ value borrowed here after move
As you can see, value was moved when map.insert(str1, str2)
was called. One important point, that I’ll just take from the book:
If we insert references to values into the hash map, the values won’t be moved into the hash map. The values that the references point to must be valid for at least as long as the hash map is valid.
(This is covered in detail in Chapter 10, so let’s save it for later).
Accessing Values in a Hash Map
Use <HashMap>.get(<Key>)
function to get the value corresponding to the key. Please note that it returns Option<&V>
where V
is the type of the value.
use std::collections::HashMap;
fn main() {
let str1 = String::from("Kush");
let str2 = String::from("Name");
let mut map = HashMap::new();
map.insert(str1, str2);
let val = map.get(&String::from("Kush"));
println!("{}", val.unwrap());
}
Outputs: Name
.
Since map.get(&String::from("Kush"))
returns Option<&String>
object, we need to unwrap()
it in order to print it. Here is an interesting post on why using unwrap()
is not the best idea, and what other options we have. I used unwrap()
here, since I knew it is not None
.
Iterating through a hash map is also easy in Rust:
// Use the same code as above
for (key, value) in &map {
println!("{}: {}", key, value);
}
// Output:
// Kush: Name
Updating a Hash Map
Note that you can only have one value corresponding to a key. Let’s consider our options:
-
Overwriting a value:
insert()
twice, and the new value will replace the old value. -
Only insert if key has no value:
- Use
entry(<Key>)
, it returns anEnum
:Empty
(represents a value that might or might not exist). - Call
or_insert
on the enum, which inserts a value if key doesn’t exist. or_insert
returns a mutable reference to the value present, or to the new value inserted.
use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50); println!("{:?}", scores); } // Outputs // {"Blue": 10, "Yellow": 50}
- Use
-
Updating a value based on the old value: Comments in the code should help explain the example.
use std::collections::HashMap; fn main() { let text = "hello world wonderful world"; let mut map = HashMap::new(); // "hello", "world", "wonderful", "world" - values of word for word in text.split_whitespace() { // if the map has an entry for word, return the count of it // else, insert 0 and increase the counter by 1 // note that count is mutable reference // so it needs to be de-referenced (in order to use it) let count = map.entry(word).or_insert(0); *count += 1; } println!("{:?}", map); } // Outputs: // {"world": 2, "wonderful": 1, "hello": 1}
Hashing Functions
By default, Hash Maps in Rust use SipHash
hashing function, but you can use your own as well - it should implement BuildHasher
trait. More on this is discussed in Chapter 10.
That’s it for Chapter 8, thank you for reading. :)
Note
These are just my notes, or things I write down while/after reading the chapters/blogs or going through resources. I like sharing them, for everyone’s and also my memory. At no point I say or mean that these should be preferred or read “over” the original resource mentioned. But as always, I’m open for feedback and/or suggestions, so feel free to comment here on the blog (just be nice, is all I ask for).