Files
docs-rust/ch08/ch08-03-hash-maps.html
2026-06-22 21:27:36 +05:30

263 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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&lt;K, V&gt;</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 teams score in a hash map in which
each key is a teams name and the values are each teams score. Given a team
name, you can retrieve its score.</p>
<p>Well go over the basic API of hash maps in this section, but many more goodies
are hiding in the functions defined on <code>HashMap&lt;K, V&gt;</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, were 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 its not included in the features brought into scope
automatically in the prelude. Hash maps also have less support from the
standard library; theres 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(&amp;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 thats associated with the Blue team, and the
result will be <code>10</code>. The <code>get</code> method returns an <code>Option&lt;&amp;V&gt;</code>; if theres 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&lt;i32&gt;</code> rather than an
<code>Option&lt;&amp;i32&gt;</code>, then <code>unwrap_or</code> to set <code>score</code> to zero if <code>scores</code> doesnt
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 &amp;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 theyre inserted</figcaption>
</figure>
<p>We arent able to use the variables <code>field_name</code> and <code>field_value</code> after
theyve 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 wont 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. Well 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>doesnt</em> already have a value. Or you could combine the old value and the
new value. Lets 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 were inserting the value for the Blue
teams 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 Isnt Present</a></h4>
<p>Its 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
doesnt 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. Lets say
we want to check whether the key for the Yellow team has a value associated
with it. If it doesnt, 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 doesnt 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 keys 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 weve
seen that word. If its the first time weve seen a word, well 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>&amp;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. Well talk about traits and how to implement them in
<a href="../ch10/ch10-02-traits.html">Chapter 10</a><!-- ignore -->. You dont 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>Were getting into more complex programs in which operations can fail, so its
a perfect time to discuss error handling. Well 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>