HashMap в Rust
HashMap (хеш-таблица) хранит данные парами «ключ — значение»: ты ищешь значение по ключу, а не по позиции, как в массиве или векторе. Если ты пользовался словарём в Python или объектом-как-словарём в JavaScript, идея та же.
Создаём HashMap
Заголовок раздела «Создаём HashMap»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);}Перебираем HashMap
Заголовок раздела «Перебираем HashMap»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
Заголовок раздела «Когда использовать HashMap»Бери HashMap, когда нужно искать по имени или идентификатору, а не по позиции: подсчёт частоты слов, сопоставление логинов с данными пользователей, кэширование результатов по ключу. Если важен порядок или ты просто идёшь по обычному списку, лучше подойдёт Vec.
Практика
Заголовок раздела «Практика»1. Маленькая телефонная книга
Заголовок раздела «1. Маленькая телефонная книга»Сохрани в 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 нужен предсказуемый вывод.
2. Подсчёт голосов
Заголовок раздела «2. Подсчёт голосов»Есть голоса ["красный", "синий", "красный", "зелёный", "синий", "красный"]. Посчитай, сколько получил каждый цвет, и выведи итоги для "красный", "зелёный", "синий" и "розовый" (за него не голосовали). Подсказка: 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розовый: 0or_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
Что почитать
Заголовок раздела «Что почитать»- Хранение ключей со связанными значениями в HashMap — книга «Язык программирования Rust»: владение ключами и значениями и пример подсчёта слов с обновлением значений (перевод на русский).
- HashMap — Rust на примерах: запускаемый пример со списком контактов:
insert,get,removeи циклы (перевод на русский). HashMap— документация стандартной библиотеки (на английском): все методы, включаяcontains_key,remove,keysиvalues.Entry— документация стандартной библиотеки (на английском): всё, что можно сделать через.entry(), напримерor_insert_withиand_modify.