In this tutorial we want to learn about HashMap by looking at several easy to understand examples.
[lwptoc]
Example 1: Rust HashMap Example
An easy to understand Rust Hashmap example.
Step 1: Dependencies
No dependencies are needed.
Step 2: Write Code
Here is the full code:
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
fn main() {
println!("Using borrowed pointers as keys.");
let mut h: HashMap<&str, isize>;
h = HashMap::new();
h.insert("foo", 42);
println!("Is there a key foo? => {}", h.contains_key(&("foo"))); // => true
println!("Is there a key baz? => {}", h.contains_key(&("baz"))); // => false
println!("The value for foo is => {:?}", h.get(&("foo"))); // => Some(&42)
let key = "baz";
h.insert(key, 1);
println!("Is there a key baz? => {}", h.contains_key(&("baz"))); // => false
// Doing a find, inserting with a proc()
, using the key to construct the value
let mut map = HashMap::<String, String>::new();
match map.entry("foo".to_string()) {
Vacant(entry) => { entry.insert("bar".to_string()); },
Occupied(mut entry) => { entry.get_mut().push_str("bar"); },
}
println!("The value for foo is => {:?}", map.get(&("foo".to_string()))); // => Some(&~"foobar")
// running this for the first time, will add "foo" with the value 1
// running the same for the second time, will add +1 to "foo"
match h.entry("foo") {
Vacant(entry) => { entry.insert(1); },
Occupied(mut entry) => { *entry.get_mut() += 1; },
}
println!("foo={:?}", h.get(&("foo")));
assert_eq!(h["foo"], 43);
// You don't actually need the HashMap to own the keys (but
// unless all keys are static, this will be likely to lead
// to problems, so I don't suggest you do it in reality)
println!("Using owned pointers as keys.");
let mut h = HashMap::<String, isize>::new();
h.insert("foo".to_string(), 42);
println!("Is there a key foo? => {}", h.contains_key(&"foo".to_string())); // => true
println!("Is there a key baz? => {}", h.contains_key(&"baz".to_string())); // => false
println!("The value for foo is => {:?}", h.get(&"foo".to_string())); // => Some(&42)
h.insert(key.to_string(), 1);
println!("Is there a key baz? => {}", h.contains_key(&"baz".to_string())); // => true
// List keys of the HashMap
let mut keys: Vec<String> = Vec::new();
for (k, _) in h.iter() {
keys.push(k.to_string());
}
println!("These are the keys: {:?}.", keys);
let keys = h.keys().map(|v| v.clone()).collect::<Vec<String>>();
println!("These are the keys: {:?}.", keys);
// List values of the HashMap
let values = h.values().map(|v| v.clone()).collect::<Vec<isize>>();
println!("These are the values: {:?}.", values);
// type inference lets us omit an explicit type signature (which
// would be HashMap<&str, &str>
in this example).
let mut book_reviews = HashMap::new();
// review some books.
book_reviews.insert("Adventures of Huckleberry Finn", "My favorite book.");
book_reviews.insert("Grimms' Fairy Tales", "Masterpiece.");
book_reviews.insert("Pride and Prejudice", "Very enjoyable.");
book_reviews.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot.");
// check for a specific one.
if !book_reviews.contains_key(&("Les Misérables")) {
println!("We've got {} reviews, but Les Misérables ain't one.",
book_reviews.len());
}
// oops, this review has a lot of spelling mistakes, let's delete it.
book_reviews.remove(&("The Adventures of Sherlock Holmes"));
// look up the values associated with some keys.
let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"];
for book in to_find.iter() {
match book_reviews.get(book) {
Some(review) => println!("{}: {}", *book, *review),
None => println!("{} is unreviewed.", *book)
}
}
// iterate over everything.
for (book, review) in book_reviews.iter() {
println!("{}: \"{}\"", *book, *review);
}
}
Step 3: Run
Run the code and you will get the following:
Using borrowed pointers as keys.
Is there a key foo? => true
Is there a key baz? => false
The value for foo is => Some(42)
Is there a key baz? => true
The value for foo is => Some("bar")
foo=Some(43)
Using owned pointers as keys.
Is there a key foo? => true
Is there a key baz? => false
The value for foo is => Some(42)
Is there a key baz? => true
These are the keys: ["baz", "foo"].
These are the keys: ["baz", "foo"].
These are the values: [1, 42].
We've got 4 reviews, but Les Misérables ain't one.
Pride and Prejudice: Very enjoyable.
Alice's Adventure in Wonderland is unreviewed.
Adventures of Huckleberry Finn: "My favorite book."
Pride and Prejudice: "Very enjoyable."
Grimms' Fairy Tales: "Masterpiece."
Example 2: Rust HashMap Example
Learn about hashmap in Rust using this example.
Step 1: Create Project
- Open your
Rust
IDE. - In the menu go to
File --> Create New Project
.
Step 2: Add Dependencies
Go to your Cargo.toml
and modify it as follows:
[package]
name = "_11_collections"
version = "0.1.0"
authors = ["Inanc Gumus <[email protected]>"]
edition = "2018"
[dependencies]
Step 3: Write Code
Next create a file known as hashmaps.rs
and add the following code:
//
// Rust's HashMap is an ordinary map type that you might already know about from other langs.
//
// -> They are stored on the heap memory.
//
// So Rust's hash map is:
//
// HashMap<K, V>
//
// HashMap is a generic type:
//
// -> Keys are of the type K
, and,
// -> Values are of type V
.
//
// first, you need to bring it to this scope.
use std::collections::HashMap;
#[allow(unused)]
pub fn run() {
// ===================================================================
// let's create a new hash map.
// ===================================================================
let mut reviews = HashMap::new();
// ===================================================================
// let's add some game reviews.
//
// key => game name (&str)
// value => review score (floating-point number)
// ===================================================================
reviews.insert("Mass Effect", 7.9);
reviews.insert("Sam and Max: Hit the Road", 9.5);
reviews.insert("Beneath a Steel Sky", 9.0);
reviews.insert("Dune", 8.0);
{
// or, you can construct it from these vectors.
let games = vec!["Mass Effect", "Sam and Max: Hit the Road"];
let scores = vec![7.9, 9.5];
// now, all you need is to zip them together into a map.
let mut reviews: HashMap<_, _> = games.into_iter().zip(scores.into_iter()).collect();
// ^ ^ | |
// |_/ v v
// Guesses the key and value Keys = &str Values = float
// types automatically <---------====---------------=====
// from the vectors
// ===================================================================
// ☝️ into_iter()
//
// Above, each into_iter()
takes ownership of a vector.
//
// So you can no longer use the games, or scores.
//
// let _ = games.get(0).unwrap();
// let _ = scores.get(0).unwrap();
//
// ===================================================================
}
// ===================================================================
// let's access the values inside a map
// ===================================================================
let mass_effect = reviews.get("Mass Effect");
println!(
"They rated Mass Effect with a score of {:?}.",
mass_effect.unwrap() // unwrap is not cool, you'll see why below.
);
// ===================================================================
// let's check for the existence of keys
// ===================================================================
//
// first, you're going to learn what happens if you try to access
// a non-existing key.
//
let ping_pong = reviews.get("Ping Pong");
//
// uncomment the following line and see what happens:
//
// println!(
// "They rated Ping Pon with a score of {:?}",
// ping_pong.unwrap()
// );
//
// now let's learn how to prevent panicking.
if let None = ping_pong {
println!("I don't know anything about the Ping Pong game.");
}
// ☝️ remember, if let
is syntactic sugar for match
.
// here, we check if ping_pong is one of the variants of
// the Option enum.
// let's check for an existing game.
let sam_and_max = "Sam and Max: Hit the Road";
if let Some(score) = reviews.get(sam_and_max) {
println!(
"But they also rated {} with a score of {:?}.",
sam_and_max, score,
);
}
// ☝️ here, we check if there is "some" value in the value returned
// by the reviews.get(sam_and_max)
.
//
// if there is, Rust binds it to the score
variable.
// ===================================================================
// let's iterate over each key/value pair
// ===================================================================
for (key, value) in &reviews {
println!("-> They rated {} with a score of {}.", key, value);
}
// ☝️ remember: borrow, do not unnecessarily own.
//
// if you've used reviews above instead of &reviews,
// then you'd be owning, not borrowing.
//
// so, you would no longer be able to use reviews.
// it would have been dropped after the loop ends.
// ===================================================================
// let's overwrite a value
// ===================================================================
// you can overwrite a value if you re-insert with the same key again.
reviews.insert("Mass Effect", 8.5);
// let's pretty print everything in the map.
println!("{:#?}", reviews);
// ===================================================================
// let's insert a value only if there isn't a key with the same name
// ===================================================================
reviews.entry("Ping Pong").or_insert(6.0);
// adds Ping Pong with a score of 6.0
reviews.entry("Ping Pong").or_insert(8.0);
// has no effect, reviews has Ping Pong
println!("{:#?}", reviews);
// ===================================================================
// let's create a program to find word frequencies in a str
// ===================================================================
let text = "that that exists exists in that that that that exists exists in";
let mut freqs = HashMap::new();
for w in text.split_whitespace() {
// split_whitespaces() returns an iterator that returns string sub-slices
// from text
. they don't re-allocate. rust is as efficient as it goes.
let c = freqs.entry(w).or_insert(0);
// c -> becomes 0 if w
key doesn't exist on the map.
// entry(w) -> returns the entry of a key from the hash map.
// or_insert(0) -> if the key was absent, inserts the key w
// with a value of 0.
*c += 1;
// otherwise:
// -> or_insert() returns the existing entry in the map
// as a mutable reference (&mut i32).
// -> so you need to dereference it to get the i32 value.
// -> finally, we can increment it.
}
// since each c goes out of scope after the loop's scope, the map's
// values can be used afterwards. otherwise, this wouldn't be possible.
// remember, rust doesn't allow immutable & mutable borrowers co-exist.
println!("{:?}", freqs);
}
Next create a file known as main.rs
and add the following code:
// https://doc.rust-lang.org/std/collections/index.html
mod vectors; // make vectors.rs act like the vectors module.
mod strings; // make strings.rs act like the strings module.
mod hashmaps; // make hashmaps.rs act like the hashmaps module.
fn main() {
println!("*******************************************************************");
println!("★ VECTORS ★");
println!("*******************************************************************");
vectors::run();
println!("\n");
println!("*******************************************************************");
println!("★ STRINGS ★");
println!("*******************************************************************");
strings::run();
println!("\n");
println!("*******************************************************************");
println!("★ HASH MAPS ★");
println!("*******************************************************************");
hashmaps::run();
}
Next create a file known as strings.rs
and add the following code:
// ===================================================================
// 🤯 OMG
// I had a hard time learning through all of these String types.
// When I try to do the collection exercises, I couldn't know what to do.
// So I tried to reread the documentation, and also a lot of posts, etc.
// I think I finally came to understand the necessary knowledge to go on.
// ===================================================================
// ===================================================================
// ☝️ Rust wants you to put more thought when working with Strings.
// So that you'll be saved from string/character bugs later on.
// ===================================================================
// ===================================================================
// ☝️ When Rustaceans refer to "strings" in Rust,
// they usually mean the String and the string slice &str types,
// not just one of those types.
// ===================================================================
// ===================================================================
// String Terminology used in Rust:
//
// ❤️ TLDR:
//
// -> Use String
: If you need an owned and mutable string data.
// -> use &str
: If you need a borrowed, and read-only string data.
//
// ⭐️ "a string literal like this one"
//
// -> A fix-sized UTF-8 encoded string slice that refers to a
// hardcoded location in memory.
// -> Underlying type: &'static str
// -> 'static
means the value is hardcoded into the binary.
//
// ⭐️ &str
//
// -> Preferred way to pass strings around.
// -> Called a string slice.
// -> It gets copied (not cloned).
//
// -> UTF-8 encoded: It's a reference to a UTF-8 byte array.
//
// => Two-words fat pointer:
// -> A pointer to a str
.
// -> The str's length.
// -> See: https://doc.rust-lang.org/std/primitive.str.html#representation
//
// => Size is only known at runtime.
//
// -> Following won't work because the size is unknown at compile-time.
// Rust needs to know the size of every variable.
//
// let impossible: str = "nope";
//
// -> This will work because &str is a reference to a location
// in memory. So its address can be known at runtime.
//
// let possible: &str = "yep";
//
// ⭐️ String
//
// -> Dynamic string type: Growable, and shrinkable.
// -> Owned, mutable, UTF-8 encoded, and heap-allocated.
//
// -> Its source code looks like this:
//
// pub struct String {
// vec: Vec<u8>,
// }
//
// => You can pass it as &String to a function that accepts &str.
// WHY?
// https://doc.rust-lang.org/std/string/struct.String.html#deref
//
//
// let s = String::from("hey");
//
// fn p(s: &str) {
// println!("{}", s);
// }
//
// p(&s);
// ^
// |
// ________/
// \ Above, Rust automatically does this:
// &*s
// ^^
// ||
// |+--> Dereferences to str
// +--> Borrows it
//
// So it becomes a &str that points to the contents of s.
//
// ⭐️ Other String Types
//
// -> OsString, OsStr, CString, and Cstr.
// -> Other crates can create their own string types
// to encode data, or represent data in memory
// in different ways.
// ===================================================================
#[allow(unused)] // see: https://kutt.it/Qh9Jfb
pub fn run() {
// ------------------------------------------------------------------------
// let's create a new empty String
// ------------------------------------------------------------------------
let mut s = String::new();
// ------------------------------------------------------------------------
// let's init another s with data
// ------------------------------------------------------------------------
let data = "initial data";
let s = data.to_string(); // converts to a String.
// if the type implements the Display trait.
// ------------------------------------------------------------------------
// let's init it using a string literal
// ------------------------------------------------------------------------
let s = "initial data".to_string();
// or you can use the from
fn, it's the same with the one above
let s = String::from("initial data");
// ------------------------------------------------------------------------
// ☝️ many ops available with Vec<T> are available with String as well.
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// updating
// ------------------------------------------------------------------------
let mut s = "hello".to_string();
let s2 = " world";
s.push_str(s2); // push_str mutably borrows s2
s += ", how are you"; // ___________/
// /
s.push('?'); // v
println!("s: {} - s2: {}", s, s2); // so you can still use it
// ------------------------------------------------------------------------
// let's concat two strings
// ------------------------------------------------------------------------
let hello = "hello".to_string();
let world = " world!".to_string();
//
// hello moves below, so it cannot be used again.
//
// this is because, the op below uses the add method of hello
.
// that method takes ownership of hello
, and borrows world
.
//
let hello_world = hello + &world;
//
// that's why you can no longer use hello.
//
// println!("{} {}", hello, world); // error: 0382
//
// this happens in the name of efficiency.
// -> add method COPIES world
to hello
's buffer.
// -> so it REUSES hello
's buffer to prevent creating a new string
// each time you concat a string to it.
// ------------------------------------------------------------------------
// let's combine multiple strings (2+) using format!()
// ------------------------------------------------------------------------
// -> format doesn't take any ownership.
// -> it just prints the contents to the screen.
// -> it doesn't make any concatenation.
let tic = "tic".to_string();
let tac = "tac".to_string();
let toe = "toe".to_string();
let tic_tac_toe = format!("{}-{tac}-{}", tic, toe, tac = tac);
// let tic_tac_toe = format!("{}-{}-{}", tic, tac, toe); // same as above
println!("{}", tic_tac_toe);
// ------------------------------------------------------------------------
// what about utf-8?
// ------------------------------------------------------------------------
// rust counts how many bytes needed to represent a UTF-8 string.
// -> for example: ü and ı are 2 bytes each.
println!("len(\"Gunaydin\") : {} bytes", "Gunaydin".len()); // 8 bytes
println!("len(\"Günaydın\") : {} bytes", "Günaydın".len()); // 10 bytes
// let's count the characters (scalar values) instead.
println!(
"\"Günaydın\".chars().count(): {} chars",
"Günaydın".chars().count()
); // 8
// ------------------------------------------------------------------------
// let's index a string, think twice.
// ------------------------------------------------------------------------
// 🦀 TLDR: "string indexing is a bad idea!"
//
// Example:
//
// "Günaydın"[1]
//
// ü is two bytes, so why do you want the first byte? it doesn't make sense.
// bailout!
//
// But, why?
//
// -> the return type isn't clear: a char? a grapheme cluster? idk.
// -> to prevent unexpected values.
// -> to prevent possibly-buggy code.
// -> to make it possible to guarantee O(1) performance.
// -> string indexing is usually O(1).
// -> however, often that may not be true for multiple-byte chars.
// -> to leave the interpretation and storing raw string data to you, the programmer.
//
// So what should you do?
// -> Use proper ranges to get a slice.
// -> For example: ü is consisting of 2 bytes, within this range: 1..3
let s = "Günaydın";
println!("Günaydın[0..1] = {}", &"Günaydın"[0..1]); // G
println!("Günaydın[1..3] = {}", &"Günaydın"[1..3]); // ü
// ⭐️ Remember: & is for borrowing.
// PANIC ERROR: 1..2 is the first byte of ü.
// -> Rust can't give you a slice like that.
// println!("Günaydın[0..1] = {}", &"Günaydın"[1..2]);
// ------------------------------------------------------------------------
// let's iterate
// ------------------------------------------------------------------------
let s = "Günaydın";
for c in s.chars() {
println!("{}", c);
}
// Behind the scenes: s.chars() calls s.as_bytes().iter() {}
// If you want to see the individual bytes, use the s.bytes()
instead.
for b in s.bytes() {
println!("{}", b);
}
//
// What does Rust iterate on with the chars() method?
//
// -> chars() iterates over Unicode scalar values.
// 😡 Is it weird? Kinda.
//
// -> Iteration over grapheme clusters may be what you actually want.
// -> This functionality is not provided by Rust's standard library,
// -> Check crates.io instead.
//
// What's a grapheme cluster?
//
// -> you can usually think of it as an ordinary alphabet letter.
// -> a character (_as we call it_) is a very different thing in the eyes of a computer.
//
// Not convinced?
//
// read this one: http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
// and this : https://en.wikipedia.org/wiki/Character_(computing)
//
}
// REFERENCES:
// https://doc.rust-lang.org/book/ch08-02-strings.html
// https://doc.rust-lang.org/std/primitive.str.html
// https://doc.rust-lang.org/std/string/struct.String.html
// https://doc.rust-lang.org/src/alloc/string.rs.html
// https://doc.rust-lang.org/src/core/str/mod.rs.html
Next create a file known as vectors.rs
and add the following code:
// ⭐️ A vector is stored on the heap memory.
pub fn run() {
//
// let's create a vector of 32-bit ints:
// __________/
// /
let mut v: Vec<i32> = Vec::new();
//
// let's create the same vector w/type-inferring:
//
// let v = vec![1, 2, 3];
//
// let's create another i32 vector that looks like this: [1, 1, 1]
.
//
// let v = vec![1; 3];
//
// let's update the vector
v.push(1);
v.push(2);
v.push(3);
println!("\nreading a vector:");
println!("first : {}", &v[0]);
println!("second: {}", v.get(1).unwrap()); // use this with match
.
//
// Borrowing and Ownership:
//
// let first = &v[0]; // immutable borrow
// v.push(4); // mutable borrow -> ERROR
// println!("first: {}", first); // immutable borrow
//
// A vector can move its elements (behind the scenes).
// That's why even borrowing the first element, then pushing
// another one at the end can create an error here.
//
println!("\nwalking over a vector:");
for i in &mut v {
// v is a Vec<i32>
// i is a &mut i32
, and *i
is an i32.
// Here, we're writing to the memory cell where i lives by dereferencing.
*i *= 2; // write to the vector element
println!("{}", i); // read the vector element
}
// To walk over immutably, use:
// for i in &v {..}
}
// Rust will drop v
here, along with its elements.
Run
Copy the code, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |