Перейти к содержимому

HashMap в Rust

HashMap (хеш-таблица) хранит данные парами «ключ — значение»: ты ищешь значение по ключу, а не по позиции, как в массиве или векторе. Если ты пользовался словарём в Python или объектом-как-словарём в JavaScript, идея та же.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Алиса", 30);
ages.insert("Боб", 25);
println!("{:?}", ages);
}

Строка use std::collections::HashMap; вверху делает тип доступным. В отличие от Vec и String, HashMap по умолчанию не подключён, так что эта строка обязательна.

▶ Попробуй сам
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Алиса", 30);
match ages.get("Алиса") {
Some(age) => println!("Алисе {age}"),
None => println!("Не найдено"),
}
}
▶ Попробуй сам

.get() возвращает не само значение, а Option, потому что такого ключа может и не быть. Подробно Option разобран в отдельном уроке, а пока стандартный способ с ним работать — match с Some/None.

Повторная вставка с тем же ключом перезаписывает старое значение:

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Алиса", 30);
ages.insert("Алиса", 31); // перезаписывает 30
println!("{:?}", ages.get("Алиса"));
}
▶ Попробуй сам

Если значение нужно вставить, только когда ключа ещё нет, используй .entry():

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Алиса", 30);
ages.entry("Алиса").or_insert(99); // Алиса уже есть, поэтому ничего не происходит
ages.entry("Боб").or_insert(25); // Боба нет, поэтому он добавляется
println!("{:?}", ages);
}
▶ Попробуй сам
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Алиса", 30);
ages.insert("Боб", 25);
for (name, age) in &ages {
println!("{name}: {age}");
}
}
▶ Попробуй сам

Бери HashMap, когда нужно искать по имени или идентификатору, а не по позиции: подсчёт частоты слов, сопоставление логинов с данными пользователей, кэширование результатов по ключу. Если важен порядок или ты просто идёшь по обычному списку, лучше подойдёт Vec.

Сохрани в HashMap три имени и номера телефонов. Потом найди "Бен" (он там есть) и "Дана" (её нет) и для каждого выведи понятную строку. Подсказка: перебери ["Бен", "Дана"] и сделай match по .get(name).

Показать решение
use std::collections::HashMap;
fn main() {
let mut phone_book = HashMap::new();
phone_book.insert("Аня", "555-1234");
phone_book.insert("Бен", "555-9876");
phone_book.insert("Чэнь", "555-4321");
for name in ["Бен", "Дана"] {
match phone_book.get(name) {
Some(number) => println!("{name}: {number}"),
None => println!("{name}: нет в телефонной книге"),
}
}
}

Вывод:

Бен: 555-9876
Дана: нет в телефонной книге
▶ Попробуй сам

Мы перебираем нужные имена, а не саму таблицу, — и заодно получаем фиксированный порядок. Это удобный приём, когда от HashMap нужен предсказуемый вывод.

Есть голоса ["красный", "синий", "красный", "зелёный", "синий", "красный"]. Посчитай, сколько получил каждый цвет, и выведи итоги для "красный", "зелёный", "синий" и "розовый" (за него не голосовали). Подсказка: counts.entry(color).or_insert(0) даёт текущий счёт для цвета, начиная с 0. Прибавь к нему единицу через *count += 1.

Показать решение
use std::collections::HashMap;
fn main() {
let votes = ["красный", "синий", "красный", "зелёный", "синий", "красный"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["красный", "зелёный", "синий", "розовый"] {
match counts.get(color) {
Some(n) => println!("{color}: {n}"),
None => println!("{color}: 0"),
}
}
}

Вывод:

красный: 3
зелёный: 1
синий: 2
розовый: 0
▶ Попробуй сам

or_insert(0) возвращает изменяемую ссылку на значение внутри таблицы, а не копию. * в *count += 1 значит «измени число, на которое она указывает», так что обновляется сама таблица. Шаблон «entry, или вставь 0, потом прибавь 1» — стандартный способ что-то посчитать в Rust.

  • HashMap<K, V> хранит пары «ключ — значение»; подключается через use std::collections::HashMap;.
  • .insert(ключ, значение) добавляет или перезаписывает; .get(ключ) ищет и возвращает Option.
  • .entry(ключ).or_insert(значение) вставляет, только если ключа ещё нет.
  • Порядок перебора не гарантирован — не полагайся на него.

Проверь себя

1. Что нужно, чтобы подключить HashMap, в отличие от Vec или String?

2. Что делает .entry(key).or_insert(value)?

3. Можно ли полагаться на порядок записей при переборе HashMap?

Результат: 0 / 3