Rewrite HashMap

pull/67/head
Dhghomon 4 years ago committed by GitHub
parent 0e05b6e32b
commit b04b935fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,10 +45,10 @@ It is now late August, and *Easy Rust* is over 400 pages long. Easy Rust is almo
- [Destructuring](#destructuring)
- [References and the dot operator](#references-and-the-dot-operator)
- [Other collections](#other-collections)
- [HashMap (and BTreeMap)](#hashmap-and-btreemap)
- [HashSet and BTreeSet](#hashset-and-btreeset)
- [BinaryHeap](#binaryheap)
- [VecDeque](#vecdeque)
- [HashMap (and BTreeMap)](#hashmap-and-btreemap)
- [HashSet and BTreeSet](#hashset-and-btreeset)
- [BinaryHeap](#binaryheap)
- [VecDeque](#vecdeque)
- [Generics](#generics)
- [Option and Result](#option-and-result)
- [Result](#result)
@ -2957,7 +2957,7 @@ Not much green.
This is where you can start to give your structs and enums some real power. To call functions on a `struct` or an `enum`, use an `impl` block. These functions are called **methods**. There are two kinds of methods in an `impl` block.
- Regular methods: these take **self** (or **&self** or **&mut self**). Regular methods use a `.`. `.clone()` is an example of a regular method.
- Regular methods: these take **self** (or **&self** or **&mut self**). Regular methods use a `.` (a period). `.clone()` is an example of a regular method.
- Associated methods (or "static" methods): these do not take self. Associated means "related to". They are written differently, using `::`. `String::from()` is an associated method, and so is `Vec::new()`. You usually see associated methods used create new variables.
In our example we are going to create animals and print them.
@ -3244,20 +3244,20 @@ So just remember: when you use the `.` operator, you don't need to worry about `
## Other collections
Rust has many more types of collections. You can see them at https://doc.rust-lang.org/beta/std/collections/ in the standard library. That page has good explanations for why to use one type, so go there if you don't know what type you want. We will start with `HashMap`, which is very common.
Rust has many more types of collections. You can see them at https://doc.rust-lang.org/beta/std/collections/ in the standard library. That page has good explanations for why to use one type, so go there if you don't know what type you want. These collections are all inside `std::collections` in the standard library. The best way to use them is with a `use` statement, like we did with our `enums`. We will start with `HashMap`, which is very common.
## HashMap (and BTreeMap)
### HashMap (and BTreeMap)
A HashMap is a collection made out of *keys* and *values*. You use the key to look up the value that matches the key. You can create a new `HashMap` with just `HashMap::new()` and use `.insert(key, value)` to insert items.
A `HashMap` is not in order, so if you print every key in a `HashMap` together it will probably print differently. We can see this in an example:
```rust
use std::collections::HashMap; // You have to bring HashMap in to use it
use std::collections::HashMap; // This is so we can just write HashMap instead of std::collections::HashMap every time
struct City {
name: String,
population: HashMap<u32, u32>, // This will have the date and the population for the date
population: HashMap<u32, u32>, // This will have the year and the population for the year
}
fn main() {
@ -3272,7 +3272,7 @@ fn main() {
tallinn.population.insert(2020, 437_619);
for (year, population) in tallinn.population { // The HashMap is HashMap<u32, u32> so it returns a tuple with two items
for (year, population) in tallinn.population { // The HashMap is HashMap<u32, u32> so it returns a two items each time
println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
}
}
@ -3294,7 +3294,9 @@ In the year 2020 the city of Tallinn had a population of 437619.
In the year 1372 the city of Tallinn had a population of 3250.
```
If you want a `HashMap` that you can sort, you can use a `BTreeMap`. Actually they are very similar to each other, so we can quickly change our `HashMap` to a `BTreeMap` to see. You will notice that it is almost the same code.
You can see that it's not in order.
If you want a `HashMap` that you can sort, you can use a `BTreeMap`. Actually they are very similar to each other, so we can quickly change our `HashMap` to a `BTreeMap` to see. You can see that it is almost the same code.
```rust
use std::collections::BTreeMap; // Just change HashMap to BTreeMap
@ -3315,7 +3317,7 @@ fn main() {
tallinn.population.insert(1851, 24_000);
tallinn.population.insert(2020, 437_619);
for (year, population) in tallinn.population.iter() { // just add .iter() - it will be sorted
for (year, population) in tallinn.population {
println!("In the year {} the city of {} had a population of {}.", year, tallinn.name, population);
}
}
@ -3331,7 +3333,9 @@ In the year 2020 the city of Tallinn had a population of 437619.
Now we will go back to `HashMap`.
You can get a value in a `HashMap` by just putting the key in `[]` square brackets. This will bring up the value for the key `Bielefeld`, which is `Germany`. But be careful, because the program will crash if there is no key. If you write `println!("{:?}", city_hashmap["Bielefeldd"]);` for example then it will crash, because `Bielefeldd` doesn't exist. If you are not sure that there will be a key, you can use `.get()` which returns an `Option`. Then you will get `None` instead of crashing the program.
You can get a value in a `HashMap` by just putting the key in `[]` square brackets. In this next example we will bring up the value for the key `Bielefeld`, which is `Germany`. But be careful, because the program will crash if there is no key. If you write `println!("{:?}", city_hashmap["Bielefeldd"]);` for example then it will crash, because `Bielefeldd` doesn't exist.
If you are not sure that there will be a key, you can use `.get()` which returns an `Option`. We will learn more about `Option` soon, but just know that an `Option` is an `enum` for when maybe there is a value and maybe not. If it exists it will be `Some(value)`, and if not you will get `None` instead of crashing the program. That's why `.get()` is the safer way to get a value from a `HashMap`.
```rust
use std::collections::HashMap;
@ -3363,9 +3367,9 @@ Some("Germany")
None
```
because *Bielefeld* exists, but *Bielefeldd* does not exist.
This is because *Bielefeld* exists, but *Bielefeldd* does not exist.
If a `HashMap` already has a key when you try to put it in, it will overwrite the value that matches it:
If a `HashMap` already has a key when you try to put it in, it will overwrite its value:
```rust
use std::collections::HashMap;
@ -3375,6 +3379,7 @@ fn main() {
book_hashmap.insert(1, "L'Allemagne Moderne");
book_hashmap.insert(1, "Le Petit Prince");
book_hashmap.insert(1, "섀도우 오브 유어 스마일");
book_hashmap.insert(1, "Eye of the World");
println!("{:?}", book_hashmap.get(&1));
@ -3393,7 +3398,7 @@ fn main() {
book_hashmap.insert(1, "L'Allemagne Moderne");
if book_hashmap.get(&1).is_none() {
if book_hashmap.get(&1).is_none() { // is_none() returns a bool: true if it's Some, false if it's None
book_hashmap.insert(1, "Le Petit Prince");
}
@ -3401,7 +3406,12 @@ fn main() {
}
```
On this subject, `HashMap` has a very interesting method called `.entry()`. With this you can try to make an entry and use another method like `.or_insert()` to insert the value if there is no key. The interesting part is that it also gives a mutable reference so you can change it. First is an example where we just insert `true` every time we insert a book title into the `HashMap`.
This prints `Some("L\'Allemagne Moderne")` because there was already a key for `1`, so we didn't insert `Le Petit Prince`.
`HashMap` has a very interesting method called `.entry()` that you definitely want to try out. With it you can try to make an entry and use another method like `.or_insert()` to insert the value if there is no key. The interesting part is that it also gives a mutable reference so you can change it if you want. First is an example where we just insert `true` every time we insert a book title into the `HashMap`.
Let's pretend that we have a library and want to keep track of our books.
```rust
use std::collections::HashMap;
@ -3414,33 +3424,43 @@ fn main() {
for book in book_collection {
book_hashmap.entry(book).or_insert(true);
}
for (book, true_or_false) in book_hashmap {
println!("Do we have {}? {}", book, true_or_false);
}
}
```
But maybe it would be better to count the number of books so that we know that there are two copies of *Eye of the World*. First let's look at what `.entry()` does, and what `.or_insert()` does. `.entry()` actually returns an `enum` called `Entry`:
This prints:
```text
Do we have Eye of the World? true
Do we have Le Petit Prince? true
Do we have L'Allemagne Moderne? true
```
But that's not exactly what we want. Maybe it would be better to count the number of books so that we know that there are two copies of *Eye of the World*. First let's look at what `.entry()` does, and what `.or_insert()` does. `.entry()` actually returns an `enum` called `Entry`:
```rust
pub fn entry(&mut self, key: K) -> Entry<K, V> // 🚧
```
[Here is the page for Entry](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html). There we can see the code for it:
[Here is the page for Entry](https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html). Here is a simple version of its code. `K` means key and `V` means variable.
```rust
// 🚧
use std::collections::hash_map::*;
pub enum Entry<'a, K: 'a, V: 'a> {
Occupied(OccupiedEntry<'a, K, V>),
Vacant(VacantEntry<'a, K, V>),
enum Entry<K, V> {
Occupied(OccupiedEntry<K, V>),
Vacant(VacantEntry<K, V>),
}
fn main() { }
```
Then when we call `.or_insert()`, it looks at the enum and decides what to do.
```rust
pub fn or_insert(self, default: V) -> &'a mut V { // 🚧
fn or_insert(self, default: V) -> &mut V { // 🚧
match self {
Occupied(entry) => entry.into_mut(),
Vacant(entry) => entry.insert(default),
@ -3448,7 +3468,7 @@ pub fn or_insert(self, default: V) -> &'a mut V { // 🚧
}
```
The interesting part is that it returns a `mut` reference. That means you can bind it to a variable, and change the variable to change the value in the `HashMap`. So for every book we will insert a 0 if there is no entry, and we will use `+= 1` on the reference. Now it looks like this:
The interesting part is that it returns a `mut` reference: `&mut V`. That means you can use `let` to attach it to a variable, and change the variable to change the value in the `HashMap`. So for every book we will insert a 0 if there is no entry. And if there is one, we will use `+= 1` on the reference to increase the number. Now it looks like this:
```rust
use std::collections::HashMap;
@ -3459,49 +3479,25 @@ fn main() {
let mut book_hashmap = HashMap::new();
for book in book_collection {
let return_value = book_hashmap.entry(book).or_insert(0);
*return_value +=1;
let return_value = book_hashmap.entry(book).or_insert(0); // return_value is a mutable reference. If nothing is there, it will be 0
*return_value +=1; // Now return_value is at least 1. And if there was another book, it will go up by 1
}
for (book, number) in book_hashmap {
println!("{:?}, {:?}", book, number);
println!("{}, {}", book, number);
}
}
```
The important part is `let return_value = book_hashmap.entry(book).or_insert(0);`. If you take out the variable, you get `book_hashmap.entry(book).or_insert(0)`. If you do that then the mutable reference just doesn't go anywhere: it inserts 0, and then has a mutable reference to 0 that nobody takes. So we bind it to `return_value` so we can keep the 0. Then we increase the value by 1, which gives at least 1 for every book in the `HashMap`. Then when `.entry()` looks at *Eye of the World* again it doesn't insert anything, but it gives us a mutable 1. Then we increase it to 2, and that's why it prints this:
```text
"L\'Allemagne Moderne", 1
"Eye of the World", 2
"Le Petit Prince", 1
```
You can also use `.or_insert_with()` which lets you use a closure. You can always just do this:
The important part is `let return_value = book_hashmap.entry(book).or_insert(0);`. If you take out the `let`, you get `book_hashmap.entry(book).or_insert(0)`. Without `let` it does nothing: it inserts 0, and nobody takes the mutable reference to 0. So we bind it to `return_value` so we can keep the 0. Then we increase the value by 1, which gives at least 1 for every book in the `HashMap`. Then when `.entry()` looks at *Eye of the World* again it doesn't insert anything, but it gives us a mutable 1. Then we increase it to 2, and that's why it prints this:
```rust
// 🚧
let return_value = book_hashmap.entry(book).or_insert_with(|| 0); // Closure with nothing
```text
L'Allemagne Moderne, 1
Le Petit Prince, 1
Eye of the World, 2
```
or add any logic that you want. Here is something simple.
```rust
// 🚧
for book in book_collection {
let return_value =
book_hashmap.entry(book).or_insert_with(|| {
if book == "Eye of the World" { // Maybe an extra copy is arriving next week
// so we know there will be at least one more
1
} else {
0
}
},
);
*return_value += 1;
}
```
You can also do things with `.or_insert()` like insert a vec and then push into the vec. Let's pretend that we asked men and women on the street what they think of a politican. They give a rating from 0 to 10. Then we want to put the numbers together to see if the politician is more popular with men or women. It can look like this:
@ -3510,7 +3506,7 @@ You can also do things with `.or_insert()` like insert a vec and then push into
use std::collections::HashMap;
fn main() {
let data = [ // This is the raw data
let data = vec![ // This is the raw data
("male", 9),
("female", 5),
("male", 0),
@ -3521,8 +3517,8 @@ fn main() {
let mut survey_hash = HashMap::new();
for item in data.iter() { // This gives a tuple of &(&str, i32)
survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1);
for item in data { // This gives a tuple of (&str, i32)
survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1); // This pushes the number into the Vec inside
}
for (male_or_female, numbers) in survey_hash {
@ -3540,7 +3536,7 @@ This prints:
The important line is: `survey_hash.entry(item.0).or_insert(Vec::new()).push(item.1);` So if it sees "female" it will check to see if there is "female" already in the `HashMap`. If not, it will insert a `Vec::new()`, then push the number in. If it sees "female" already in the `HashMap`, it will not insert a new Vec, and will just push the number into it.
## HashSet and BTreeSet
### HashSet and BTreeSet
A `HashSet` is actually a `HashMap` that only has keys. On [the page for HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html) it explains this on the top:
@ -3628,7 +3624,7 @@ fn main() {
Now it will print in order.
## BinaryHeap
### BinaryHeap
A `BinaryHeap` is an interesting collection type, because it is mostly unordered but has a bit of order. It is a collection that keeps the largest item in the front, but the other items are in any order.
@ -3706,7 +3702,7 @@ You need to: Plan who to hire next for the team
You need to: Watch some YouTube
```
## VecDeque
### VecDeque
A `VecDeque` is a `Vec` that is good at popping items both off the front and the back. When you use `.pop()` on a `Vec`, it just takes off the last item on the right and nothing else is moved. But if you take it off another part, all the items to the right are moved over one position to the left. You can see this in the description for `.remove()`:

Loading…
Cancel
Save