Встроенный тип HashMap<K, V> хранит набор элементов, где каждый элемент имеет ключ и значение. В различных языках программирования есть похожие типы данных, которые могут называться словарями, ассоциативными массивами, хеш-таблицами, картами/хеш-картами. В дальнейшем я буду называть его словарем. HashMap типизируется двумя параметрами - K и V. Параметр K устанавливает тип ключей, а параметр V - тип значений элементов.
HashMap располагается в пакете std::collections, поэтому при использовании HashMap нам надо импортировать данный тип с помощью оператора use:
use std::collections::HashMap;
Для создания пустого объекта HashMap применяется функция HashMap::new():
use std::collections::HashMap; let dictionary: HashMap<String, String> = HashMap::new();
В данном случае определяется HashMap, в котором и ключи и значения представляют тип String.
Также можно создать словарь из массива кортежей, где в каждом из кортежей по два элемента:
use std::collections::HashMap;
fn main() {
let people = HashMap::from([
("Alice", 35),
("Tom", 39)
]);
println!("{:?}", people); // {"Alice": 35, "Tom": 39}
}
В данном случае словарь people представляет тип HashMap<&str, i32>, то есть строки выступают ключами, а числа - значениями. В нем определены два элемента. Для вывода словаря на консоль используется строка форматирования "{:?}"
Также можно создать словарь из разных типов коллекций, например, вектора, с помощью метода collect():
use std::collections::HashMap;
fn main() {
let raw_data = vec![ ("Alice", 35), ("Tom", 39) ];
let people: HashMap<_, _> = raw_data.iter().cloned().collect();
println!("{:?}", people); // {"Alice": 35, "Tom": 39}
}
Здесь с помощью вызова метода iter() создается итератор, с помощью метода cloned() копируем из него данные, и затем трансформируем их в HashMap вызовом метода collect()
С помощью метода len() можно получить количество элементов:
use std::collections::HashMap;
fn main() {
let people = HashMap::from([
("Alice", 35),
("Tom", 39)
]);
println!("len: {}", people.len()); // len: 2
}
Для добавления данных в HashMap применяется метод insert(). Он принимает два параметра. Первый параметр представляет ключ элемента, а второй - значение элемента:
use std::collections::HashMap;
fn main() {
let mut people: HashMap<&str, i32> = HashMap::new();
people.insert("Tom", 39);
people.insert("Bob", 43);
println!("{:?}", people); // {"Tom": 39, "Bob": 43}
}
В данном случае мы предполагаем, что HashMap будет хранить информацию о людях, где ключ - имя, а значение - возраст человека. Поскольку здесь меняется содержимое HashMap, то переменная определяется с оператором mut.
Следует отметить, что после определения типа, мы должны строго придерживаться типа добавляемых ключей и значений. Так, в данном случае
все ключи должны строго представлять строки, и значения элементов тоже должны строго представлять тип i32.
Если за определением HashMap следуют операции добавления, то Rust может сам вывести тип ключей и значений элементов после первого добавления, соответственно тип можно не указывать.
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Bob", 43);
println!("{:?}", people); // {"Tom": 39, "Bob": 43}
}
Для значений типов, которые реализуют трейт Copy (встроенные числовые типы, bool, char, массивы, кортежи), создается копия, которая передается в HashMap.
Для остальных типов, которые не реализуют трейт Copy (как, например, String) значения напрямую
перемещаются в HashMap, и HashMap становится новым владельцем этих значений. Так, рассмотрим следующий пример:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
let age = 39;
let name = String::from("Tom");
people.insert(name, age);
println!("name: {}", name); // ! Ошибка
println!("age: {}", age); //
}
При компиляции этой программы мы столкнемся с ошибкой. Потому что при добавлении элемента в HashMap:
people.insert(name, age);
владение объектом String перейдет к переменной people, а переменную name больше нельзя использовать (если только заново ее инициалировать, но это будет уже другой объект).
Если же мы не хотим передавать владение объектом String в HashMap, то мы можем передать в метод insert() не сам объект, а ссылку на него:
people.insert(&name, age);
В этом случае ошибки не возникнет, мы по прежнему сможем обращаться к объекту через переменную name. Однако надо учитывать, что эта ссылка в HashMap будет действительна, пока существует переменная name.
Для обращения к значению элемента после названия HashMap в квадратных скобках указывается ключ:
переменная_HashMap[ключ]
Например:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
let tom_age = people["Tom"]; // Получим значение элемента с ключом "Tom"
println!("Tom's age: {}", tom_age);
}
В случае, если ключи представляют строки, то в качестве ключа мы можем передавать строковый литерал. Так, в данном случае получаем элемент с ключом "Tom".
Однако, если мы пытаемся получить элемент с ключом, которого не существует в HashMap, то при выполнении программы мы столкнемся с ошибкой:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
let bob_age = people["Bob"]; // ! Ошибка: no entry found for key
println!("age: {}", bob_age);
}
Чтобы избежать подобной ошибки, можно использовать метод get(). Этот метод также принимает ключ элемента, но возвращает
константу перечисления Option. Если элемент с требуемым ключом отсутствует в HashMap, то метод get()
возвращает константу None. Соответственно перед использованием значения мы можем проверить на эту константу:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
let bob_age = people.get("Bob");
if bob_age!=Option::None {
println!("age: {}", bob_age.unwrap());
}
else{
println!("Key not found");
}
}
Если ключ имеется в HashMap, то возвращается константа Some, которая содержит значение элемента с данным ключом. И с помощью метода unwrap() мы можем получить это значение.
Для перебора HashMap можно использовать цикл for:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
for (key, value) in &people{
println!("{}: {}", key, value);
}
}
Цикл пробегает по всем элементам HashMap и помещает в переменную key ключ, а в переменную value - значение элемента.
Обратите внимание, что в цикл передается не сам объект HashMap, а ссылка на него, чтобы избежать смену владения объектом HashMap.
Используя методы keys() и values() можно по отдельности пройтись по ключам и значениям соответственно:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
for name in people.keys(){
println!("{}", name);
}
for age in people.values(){
println!("{}", age);
}
}
// Консольный вывод
// Tom
// Alice
// 39
// 35
Метод contains_key() возвращает true, если элемент с указанным ключом существует, и false, если элемент
не существует:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
if people.contains_key("Alice"){
println!("{}", people["Alice"]);
}
else{
println!("Элемента Alice не существует");
}
if people.contains_key("Bob"){
println!("{}", people["Bob"]);
}
else{
println!("Элемента Bob не существует");
}
}
Консольный вывод:
35 Элемента Bob не существует
Данный метод можно применять в качестве альтернативы применения перечисления Option при получении элемента.
В HashMap может существовать только один элемент с определенным ключом. Попытка добавления элемента с ключом, который уже есть в HashMap, приведет к затиранию старого элемента:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
people.insert("Tom", 40);
println!("{:?}", people); // {"Alice": 35, "Tom": 40}
}
Однако такое поведение может быть нежелательным. Возможно, мы хотим добавить элемент, только если элемента с подобным ключом еще нет в HashMap. В этом случае мы можем проверять наличие ключа с
помощью метода contains_key():
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
if !people.contains_key("Tom"){
people.insert("Tom", 40);
}
println!("{:?}", people); // {"Alice": 35, "Tom": 39}
}
Но HashMap также предоставляют специальный API с помощью метода entry(). Этот метод принимает ключ элемента и возвращает перечисление Entry, которое представляет значение элемента. Затем у перечисления Entry вызывается метод or_insert(), в который передается добавляемое значение. Если элемент с ключом уже есть, то метод просто возвращает изменяемую (mutable) ссылку на значение в HashMap. Если элемент отсутствует, то метод добавляет новое значение и также возвращает изменяемую (mutable) ссылку.
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.entry("Alice").or_insert(35);
people.entry("Tom").or_insert(40);
println!("{:?}", people); // {"Alice": 35, "Tom": 39}
}
Еще один метод - and_modify() позволяет изменить значение:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.entry("Alice").or_insert(35);
people
.entry("Tom")
.and_modify(|age| *age += 1) // Если элемент есть, прибавляем к значению 1
.or_insert(39); // Если элемента нет, добавляем значение 39
println!("{:?}", people); // {"Alice": 35, "Tom": 40}
}
Иетод code>and_modify() принимает функцию, параметр которой - текущее значение элемента. В данном случае просто прибавляем 1 к значению элемента.
Для удаления одного элемента применяется метод remove(), в который передается ключ удаляемого элемента:
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
people.remove("Tom"); // удаляем элемент с ключом "Tom"
println!("{:?}", people); // {"Alice": 35}
}
Для удаления всех элементов используется метод clear():
use std::collections::HashMap;
fn main() {
let mut people = HashMap::new();
people.insert("Tom", 39);
people.insert("Alice", 35);
people.clear();
println!("{:?}", people); // { }
}
Это только небольшой перечень методов структуры HashMap. Полный список методов можно найти в документации на странице https://doc.rust-lang.org/std/collections/struct.HashMap.html.