mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-07-06 11:07:12 -06:00
finished ch8
This commit is contained in:
208
Hash.md
Normal file
208
Hash.md
Normal file
@ -0,0 +1,208 @@
|
||||
# Hash Map
|
||||
Hash maps are defined as `HashMap<K, V>`, where it stores a mapping of keys of type `K` to values of type `V` using a hashing function.
|
||||
|
||||
This determines how it places these keys and values into memory
|
||||
|
||||
Other programming support this kind of data structure its often called a different name, such as *hash*, *map*, *object*, *hash table*, *dictionary*, or *associative array*, these are just some of the names
|
||||
|
||||
Hash maps are useful when you want to look up data not by using an index, as you can with vectors, but instead you use a key that can be any type
|
||||
|
||||
For example it could be different teams scores where the name of the team is the key and the score is the value
|
||||
|
||||
Check the std library documentation for more info
|
||||
|
||||
## Creating a New Hash Map
|
||||
One way to create an empty hash map is to use `new` and to add elements with `insert`
|
||||
|
||||
Lets say we are keeping track of the score of two teams whose names are *Blue* and *Yellow*
|
||||
|
||||
Blue team has 10 points
|
||||
Yellow team has 50 points
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Yellow"), 50);
|
||||
```
|
||||
|
||||
Note we must bring HashMap into scope because it is not included by default
|
||||
|
||||
Hash Maps also has less support from the std library, therefore there is no macros to construct them and there are less methods associated with them
|
||||
|
||||
They are store on the heap
|
||||
The keys must be all be the same type as each other
|
||||
The values must be all be the same type as each other
|
||||
|
||||
Keys and Values do not need to be the same type as each other
|
||||
|
||||
## Accessing Values in a Hash Map
|
||||
|
||||
We can get a value out of the hash map by using its key to the `get` method
|
||||
|
||||
Here is an example
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Yellow"), 50);
|
||||
|
||||
let team_name = String::from("Blue");
|
||||
let score = scores.get(&team_name).copied().unwrap_or(0);
|
||||
```
|
||||
|
||||
This would result in score being equal to 10
|
||||
|
||||
The `get` method returns an `Option<&V>`, if there is no value for that key in the hash map, then the Option will be `None`
|
||||
|
||||
This program uses `copied` to get an `Option<i32>` rather than a `Option<&i32>` then `unwrap_or` to set score to zero if there is no entry for the key
|
||||
|
||||
You can iterate over each key-value pair in the hash map in a similar way as vectors using a `for` loop
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Yellow"), 50);
|
||||
|
||||
for (key, value) in &scores {
|
||||
println!("{key}: {value}");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
This would output each pair in an arbitrary order
|
||||
```
|
||||
Yellow: 50
|
||||
Blue: 10
|
||||
```
|
||||
|
||||
## Hash Maps and Ownership
|
||||
|
||||
If a type implements the `Copy` trait values are copied into the hash map
|
||||
For owned values like `String` the values with be moved and the hash map will be the owner of those values
|
||||
|
||||
Example
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let field_name = String::from("Favorite color");
|
||||
let field_value = String::from("Blue");
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(field_name, field_value);
|
||||
// field_name and field_value are invalid at this point, try using them and
|
||||
// see what compiler error you get!
|
||||
```
|
||||
|
||||
You aren't able to use `field_name` nor `field_value` because ownership of the associated value from those two variables has changed
|
||||
|
||||
This change happened after the `insert` method was called
|
||||
|
||||
If references are inserting into the hash map, then the values wont be moved but the reference MUST be valid for as long as the hash map is also valid
|
||||
|
||||
## Updating a Hash Map
|
||||
|
||||
While a Hash Map is growable each key MUST by unique and can only be associated with a single value at a time.
|
||||
The reverse is not true
|
||||
|
||||
When you want to change the data in a hash map, you have to decided how to handle the case when a key already has a value assigned
|
||||
|
||||
### Overwriting a Value
|
||||
|
||||
If you insert a key and value into a hash map then insert the same key with a different value then the value associated with the key will be overwritten
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Blue"), 25);
|
||||
|
||||
println!("{scores:?}");
|
||||
```
|
||||
|
||||
This only contains 1 key: value
|
||||
This will output `{"Blue": 25}`
|
||||
The original 10 is overwritten
|
||||
|
||||
### Adding a Key and Value Only If a Key Isn't Present
|
||||
This is common do do if there is no key associated with a desired value, then add the key and value otherwise do nothing.
|
||||
|
||||
Hash Maps have a special API for this `entry` that takes the key you want to check as a parameter
|
||||
|
||||
The return value of the `entry` method is an enum called Entry that represents a value that might or might not exist
|
||||
|
||||
This can be used if it doesn't have a value associated with the key then insert the value
|
||||
|
||||
Example of use
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
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:?}");
|
||||
```
|
||||
|
||||
The `or_insert` method on `Entry` is defined to return a mutable reference to the value for the corresponding `Entry` key if that key exists and if not it inserts the parameter as the new value for this key and returns a mutable refer to the new value
|
||||
|
||||
This is more clear then writing our own definition that complies with the borrow checker
|
||||
|
||||
This code will output `{"Yellow": 50, "Blue": 10}`
|
||||
|
||||
This means that this `or_insert` will not change it if it already exists
|
||||
|
||||
### Updating a Value Based on the Old Value
|
||||
Another common use case for hash maps is to look up a key's value then update it based on the old value
|
||||
|
||||
Example
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let text = "hello world wonderful world";
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for word in text.split_whitespace() {
|
||||
let count = map.entry(word).or_insert(0);
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
println!("{map:?}");
|
||||
```
|
||||
|
||||
This counts how many times each word appears in some text
|
||||
|
||||
If its the first time we see that word we initialize it to zero, then we add one using a reference to that key's value
|
||||
|
||||
This will output `{"world": 2, "hello": 1, "wonderful": 1}`
|
||||
|
||||
Output order is arbitrary when iterating over a hash map
|
||||
|
||||
`split_whitespace` method returns an iterator over sub slices, separated by whitespace, of the value in `text`.
|
||||
|
||||
`or_insert` method returns a mutable reference (`&mut V`) to the value for the specified key
|
||||
|
||||
In this case this reference is stored in `count` variable, then it is dereferenced `count` using `*` to assign a value to it. The reference then goes out of scope which ensures that the reference is freed and allows for the new assignment of another mutable reference to `count`. This makes all of the changes by the mutable reference safe because of the use of a `for` loop.
|
||||
|
||||
## Hashing Functions
|
||||
|
||||
By default `HashMap` uses a hashing function called [*SipHash*](https://en.wikipedia.org/wiki/SipHash) that can provided resistance to DoS (denial-of-service) attacks involving hash tables
|
||||
|
||||
This is not the fastest hashing algorithm available, but it the trade-off is for better security but with a drop in performance. This is worth it.
|
||||
|
||||
If your use case requires a different or faster hash you can switch by specifying a different hasher
|
||||
|
||||
A *hasher* is a type that implements the `BuildHasher` trait
|
||||
|
||||
Many other types of hashers can be provided in library crates which can be found on [crates.io](https://crates.io) which implement many common hashing algorithms.
|
Reference in New Issue
Block a user