feat: added cleanscript
This commit is contained in:
262
ch08/ch08-03-hash-maps.html
Normal file
262
ch08/ch08-03-hash-maps.html
Normal file
@@ -0,0 +1,262 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Storing Keys with Associated Values in Hash Maps</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="storing-keys-with-associated-values-in-hash-maps"><a class="header" href="#storing-keys-with-associated-values-in-hash-maps">Storing Keys with Associated Values in Hash Maps</a></h2>
|
||||
<p>The last of our common collections is the hash map. The type <code>HashMap<K, V></code>
|
||||
stores a mapping of keys of type <code>K</code> to values of type <code>V</code> using a <em>hashing
|
||||
function</em>, which determines how it places these keys and values into memory.
|
||||
Many programming languages support this kind of data structure, but they often
|
||||
use a different name, such as <em>hash</em>, <em>map</em>, <em>object</em>, <em>hash table</em>,
|
||||
<em>dictionary</em>, or <em>associative array</em>, just to name a few.</p>
|
||||
<p>Hash maps are useful when you want to look up data not by using an index, as
|
||||
you can with vectors, but by using a key that can be of any type. For example,
|
||||
in a game, you could keep track of each team’s score in a hash map in which
|
||||
each key is a team’s name and the values are each team’s score. Given a team
|
||||
name, you can retrieve its score.</p>
|
||||
<p>We’ll go over the basic API of hash maps in this section, but many more goodies
|
||||
are hiding in the functions defined on <code>HashMap<K, V></code> by the standard library.
|
||||
As always, check the standard library documentation for more information.</p>
|
||||
<h3 id="creating-a-new-hash-map"><a class="header" href="#creating-a-new-hash-map">Creating a New Hash Map</a></h3>
|
||||
<p>One way to create an empty hash map is to use <code>new</code> and to add elements with
|
||||
<code>insert</code>. In Listing 8-20, we’re keeping track of the scores of two teams whose
|
||||
names are <em>Blue</em> and <em>Yellow</em>. The Blue team starts with 10 points, and the
|
||||
Yellow team starts with 50.</p>
|
||||
<figure class="listing" id="listing-8-20">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Yellow"), 50);
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-20">Listing 8-20</a>: Creating a new hash map and inserting some keys and values</figcaption>
|
||||
</figure>
|
||||
<p>Note that we need to first <code>use</code> the <code>HashMap</code> from the collections portion of
|
||||
the standard library. Of our three common collections, this one is the least
|
||||
often used, so it’s not included in the features brought into scope
|
||||
automatically in the prelude. Hash maps also have less support from the
|
||||
standard library; there’s no built-in macro to construct them, for example.</p>
|
||||
<p>Just like vectors, hash maps store their data on the heap. This <code>HashMap</code> has
|
||||
keys of type <code>String</code> and values of type <code>i32</code>. Like vectors, hash maps are
|
||||
homogeneous: All of the keys must have the same type, and all of the values
|
||||
must have the same type.</p>
|
||||
<h3 id="accessing-values-in-a-hash-map"><a class="header" href="#accessing-values-in-a-hash-map">Accessing Values in a Hash Map</a></h3>
|
||||
<p>We can get a value out of the hash map by providing its key to the <code>get</code>
|
||||
method, as shown in Listing 8-21.</p>
|
||||
<figure class="listing" id="listing-8-21">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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);
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-21">Listing 8-21</a>: Accessing the score for the Blue team stored in the hash map</figcaption>
|
||||
</figure>
|
||||
<p>Here, <code>score</code> will have the value that’s associated with the Blue team, and the
|
||||
result will be <code>10</code>. The <code>get</code> method returns an <code>Option<&V></code>; if there’s no
|
||||
value for that key in the hash map, <code>get</code> will return <code>None</code>. This program
|
||||
handles the <code>Option</code> by calling <code>copied</code> to get an <code>Option<i32></code> rather than an
|
||||
<code>Option<&i32></code>, then <code>unwrap_or</code> to set <code>score</code> to zero if <code>scores</code> doesn’t
|
||||
have an entry for the key.</p>
|
||||
<p>We can iterate over each key-value pair in a hash map in a similar manner as we
|
||||
do with vectors, using a <code>for</code> loop:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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}");
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>This code will print each pair in an arbitrary order:</p>
|
||||
<pre><code class="language-text">Yellow: 50
|
||||
Blue: 10
|
||||
</code></pre>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="hash-maps-and-ownership"></a></p>
|
||||
<h3 id="managing-ownership-in-hash-maps"><a class="header" href="#managing-ownership-in-hash-maps">Managing Ownership in Hash Maps</a></h3>
|
||||
<p>For types that implement the <code>Copy</code> trait, like <code>i32</code>, the values are copied
|
||||
into the hash map. For owned values like <code>String</code>, the values will be moved and
|
||||
the hash map will be the owner of those values, as demonstrated in Listing 8-22.</p>
|
||||
<figure class="listing" id="listing-8-22">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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!
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-22">Listing 8-22</a>: Showing that keys and values are owned by the hash map once they’re inserted</figcaption>
|
||||
</figure>
|
||||
<p>We aren’t able to use the variables <code>field_name</code> and <code>field_value</code> after
|
||||
they’ve been moved into the hash map with the call to <code>insert</code>.</p>
|
||||
<p>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. We’ll talk more about these issues in
|
||||
<a href="../ch10/ch10-03-lifetime-syntax.html#validating-references-with-lifetimes">“Validating References with
|
||||
Lifetimes”</a><!-- ignore --> in Chapter 10.</p>
|
||||
<h3 id="updating-a-hash-map"><a class="header" href="#updating-a-hash-map">Updating a Hash Map</a></h3>
|
||||
<p>Although the number of key and value pairs is growable, each unique key can
|
||||
only have one value associated with it at a time (but not vice versa: For
|
||||
example, both the Blue team and the Yellow team could have the value <code>10</code>
|
||||
stored in the <code>scores</code> hash map).</p>
|
||||
<p>When you want to change the data in a hash map, you have to decide how to
|
||||
handle the case when a key already has a value assigned. You could replace the
|
||||
old value with the new value, completely disregarding the old value. You could
|
||||
keep the old value and ignore the new value, only adding the new value if the
|
||||
key <em>doesn’t</em> already have a value. Or you could combine the old value and the
|
||||
new value. Let’s look at how to do each of these!</p>
|
||||
<h4 id="overwriting-a-value"><a class="header" href="#overwriting-a-value">Overwriting a Value</a></h4>
|
||||
<p>If we insert a key and a value into a hash map and then insert that same key
|
||||
with a different value, the value associated with that key will be replaced.
|
||||
Even though the code in Listing 8-23 calls <code>insert</code> twice, the hash map will
|
||||
only contain one key-value pair because we’re inserting the value for the Blue
|
||||
team’s key both times.</p>
|
||||
<figure class="listing" id="listing-8-23">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Blue"), 25);
|
||||
|
||||
println!("{scores:?}");
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-23">Listing 8-23</a>: Replacing a value stored with a particular key</figcaption>
|
||||
</figure>
|
||||
<p>This code will print <code>{"Blue": 25}</code>. The original value of <code>10</code> has been
|
||||
overwritten.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="only-inserting-a-value-if-the-key-has-no-value"></a></p>
|
||||
<h4 id="adding-a-key-and-value-only-if-a-key-isnt-present"><a class="header" href="#adding-a-key-and-value-only-if-a-key-isnt-present">Adding a Key and Value Only If a Key Isn’t Present</a></h4>
|
||||
<p>It’s common to check whether a particular key already exists in the hash map
|
||||
with a value and then to take the following actions: If the key does exist in
|
||||
the hash map, the existing value should remain the way it is; if the key
|
||||
doesn’t exist, insert it and a value for it.</p>
|
||||
<p>Hash maps have a special API for this called <code>entry</code> that takes the key you
|
||||
want to check as a parameter. The return value of the <code>entry</code> method is an enum
|
||||
called <code>Entry</code> that represents a value that might or might not exist. Let’s say
|
||||
we want to check whether the key for the Yellow team has a value associated
|
||||
with it. If it doesn’t, we want to insert the value <code>50</code>, and the same for the
|
||||
Blue team. Using the <code>entry</code> API, the code looks like Listing 8-24.</p>
|
||||
<figure class="listing" id="listing-8-24">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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:?}");
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-24">Listing 8-24</a>: Using the <code>entry</code> method to only insert if the key does not already have a value</figcaption>
|
||||
</figure>
|
||||
<p>The <code>or_insert</code> method on <code>Entry</code> is defined to return a mutable reference to
|
||||
the value for the corresponding <code>Entry</code> key if that key exists, and if not, it
|
||||
inserts the parameter as the new value for this key and returns a mutable
|
||||
reference to the new value. This technique is much cleaner than writing the
|
||||
logic ourselves and, in addition, plays more nicely with the borrow checker.</p>
|
||||
<p>Running the code in Listing 8-24 will print <code>{"Yellow": 50, "Blue": 10}</code>. The
|
||||
first call to <code>entry</code> will insert the key for the Yellow team with the value
|
||||
<code>50</code> because the Yellow team doesn’t have a value already. The second call to
|
||||
<code>entry</code> will not change the hash map, because the Blue team already has the
|
||||
value <code>10</code>.</p>
|
||||
<h4 id="updating-a-value-based-on-the-old-value"><a class="header" href="#updating-a-value-based-on-the-old-value">Updating a Value Based on the Old Value</a></h4>
|
||||
<p>Another common use case for hash maps is to look up a key’s value and then
|
||||
update it based on the old value. For instance, Listing 8-25 shows code that
|
||||
counts how many times each word appears in some text. We use a hash map with
|
||||
the words as keys and increment the value to keep track of how many times we’ve
|
||||
seen that word. If it’s the first time we’ve seen a word, we’ll first insert
|
||||
the value <code>0</code>.</p>
|
||||
<figure class="listing" id="listing-8-25">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
|
||||
</span> 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:?}");
|
||||
<span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-8-25">Listing 8-25</a>: Counting occurrences of words using a hash map that stores words and counts</figcaption>
|
||||
</figure>
|
||||
<p>This code will print <code>{"world": 2, "hello": 1, "wonderful": 1}</code>. You might see
|
||||
the same key-value pairs printed in a different order: Recall from <a href="#accessing-values-in-a-hash-map">“Accessing
|
||||
Values in a Hash Map”</a><!-- ignore --> that iterating over a hash map
|
||||
happens in an arbitrary order.</p>
|
||||
<p>The <code>split_whitespace</code> method returns an iterator over subslices, separated by
|
||||
whitespace, of the value in <code>text</code>. The <code>or_insert</code> method returns a mutable
|
||||
reference (<code>&mut V</code>) to the value for the specified key. Here, we store that
|
||||
mutable reference in the <code>count</code> variable, so in order to assign to that value,
|
||||
we must first dereference <code>count</code> using the asterisk (<code>*</code>). The mutable
|
||||
reference goes out of scope at the end of the <code>for</code> loop, so all of these
|
||||
changes are safe and allowed by the borrowing rules.</p>
|
||||
<h3 id="hashing-functions"><a class="header" href="#hashing-functions">Hashing Functions</a></h3>
|
||||
<p>By default, <code>HashMap</code> uses a hashing function called <em>SipHash</em> that can provide
|
||||
resistance to denial-of-service (DoS) attacks involving hash
|
||||
tables<sup class="footnote-reference" id="fr-siphash-1"><a href="#footnote-siphash">1</a></sup><!-- ignore -->. This is not the fastest hashing algorithm
|
||||
available, but the trade-off for better security that comes with the drop in
|
||||
performance is worth it. If you profile your code and find that the default
|
||||
hash function is too slow for your purposes, you can switch to another function
|
||||
by specifying a different hasher. A <em>hasher</em> is a type that implements the
|
||||
<code>BuildHasher</code> trait. We’ll talk about traits and how to implement them in
|
||||
<a href="../ch10/ch10-02-traits.html">Chapter 10</a><!-- ignore -->. You don’t necessarily have to implement
|
||||
your own hasher from scratch; <a href="https://crates.io/">crates.io</a><!-- ignore -->
|
||||
has libraries shared by other Rust users that provide hashers implementing many
|
||||
common hashing algorithms.</p>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>Vectors, strings, and hash maps will provide a large amount of functionality
|
||||
necessary in programs when you need to store, access, and modify data. Here are
|
||||
some exercises you should now be equipped to solve:</p>
|
||||
<ol>
|
||||
<li>Given a list of integers, use a vector and return the median (when sorted,
|
||||
the value in the middle position) and mode (the value that occurs most
|
||||
often; a hash map will be helpful here) of the list.</li>
|
||||
<li>Convert strings to Pig Latin. The first consonant of each word is moved to
|
||||
the end of the word and <em>ay</em> is added, so <em>first</em> becomes <em>irst-fay</em>. Words
|
||||
that start with a vowel have <em>hay</em> added to the end instead (<em>apple</em> becomes
|
||||
<em>apple-hay</em>). Keep in mind the details about UTF-8 encoding!</li>
|
||||
<li>Using a hash map and vectors, create a text interface to allow a user to add
|
||||
employee names to a department in a company; for example, “Add Sally to
|
||||
Engineering” or “Add Amir to Sales.” Then, let the user retrieve a list of
|
||||
all people in a department or all people in the company by department, sorted
|
||||
alphabetically.</li>
|
||||
</ol>
|
||||
<p>The standard library API documentation describes methods that vectors, strings,
|
||||
and hash maps have that will be helpful for these exercises!</p>
|
||||
<p>We’re getting into more complex programs in which operations can fail, so it’s
|
||||
a perfect time to discuss error handling. We’ll do that next!</p>
|
||||
<hr>
|
||||
<ol class="footnote-definition">
|
||||
<li id="footnote-siphash">
|
||||
<p><a href="https://en.wikipedia.org/wiki/SipHash">https://en.wikipedia.org/wiki/SipHash</a> <a href="#fr-siphash-1">↩</a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user