Встроенный тип 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 countries: HashMap<String, String> = HashMap::new();
В данном случае определяется HashMap, в котором и ключи и значения представляют тип String
.
Для добавления данных в HashMap применяется метод insert(). Он принимает два параметра. Первый параметр представляет ключ элемента, а второй - значение элемента:
use std::collections::HashMap; fn main(){ let mut countries: HashMap<String, i32> = HashMap::new(); countries.insert(String::from("Германия"), 82); countries.insert(String::from("Франция"), 67); println!("Число элементов: {}", countries.len()); }
В данном случае мы предполагаем, что HashMap будет хранить информацию о странах, где ключ - название страны, а значение - население страны в миллионнах жителей. Поскольку здесь меняется содержимое объекта countries, то переменная определяется с оператором mut.
С помощью метода len() можно получить количество элементов.
Следует отметить, что после определения типа, мы должны строго придерживаться типа добавляемых ключей и значений. Так, в данном случае
все ключи должны строго представлять тип String
, и значения элементов тоже должны строго представлять тип i32
.
Если за определением HashMap следуют операции добавления, то Rust может сам вывести тип ключей и значений элементов после первого добавления, соответственно тип можно не указывать.
let mut countries = HashMap::new(); countries.insert(String::from("Германия"), 82); countries.insert(String::from("Франция"), 67);
Для значений типов, которые реализуют трейт Copy (встроенные числовые типы, bool, char, массивы, кортежи), создается копия, которая передается в HashMap.
Для остальных типов, которые не реализуют трейт Copy (как, например, String
) значения напрямую
перемещаются в HashMap, и HashMap становится новым владельцем этих значений. Так, рассмотрим следующий пример:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); let population = 82; let country = String::from("Германия"); countries.insert(country, population); println!("population: {} млн. чел.", population); // population можн использовать println!("country: {}", country); // ! Ошибка country нельзя использовать }
При компиляции этой программы мы столкнемся с ошибкой. Потому что при добавлении элемента в HashMap:
countries.insert(country, population);
владение объектом String перейдет к переменной countries, а переменную country
больше нельзя использовать (если только заново ее инициалировать, но это будет уже другой объект).
Если же мы не хотим передавать владение объектом String в HashMap, то мы можем передать в метод insert()
не сам объект, а ссылку на него:
countries.insert(&country, population);
В этом случае ошибки не возникнет, мы по прежнему сможем обращаться к объекту через переменную country. Однако надо учитывать, что эта ссылка в HashMap будет действительна, пока существует переменная country.
Для обращения к значению элемента после названия HashMap в квадратных скобках указывается ключ:
переменная_HashMap[ключ]
Например:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); let population = countries["Germany"]; // Получим значение элемента с ключом "" println!("Germany : {}", population); }
В случае, если ключи представляют тип String, то в качестве ключа мы можем передавать строковый литерал. Так, в данном случае получаем элемент с ключом "Germany".
Однако, если мы пытаемся получить элемент с ключом, которого не существует в HashMap, то при выполнении программы мы столкнемся с ошибкой:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); let population = countries["France"]; // элемента с ключом "France" не существует println!("France : {}", population); }
Чтобы избежать подобной ошибки, можно использовать метод get(). Этот метод также принимает ключ элемента, но возвращает
константу перечисления Option. Если элемент с требуемым ключом отсутствует в HashMap, то метод get()
возвращает константу None. Соответственно перед использованием значения мы можем проверить на эту константу:
let population = countries.get("France"); // Получим значение элемента с ключом "France" if population == Option::None{ println!("Элемента с ключом France не существует"); }
Если ключ имеется в HashMap, то возвращается константа Some, которая содержит значение элемента с данным ключом. И с помощью метода unwrap() мы можем получить это значение:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); let population = countries.get("France"); // Получим значение элемента с ключом "France" if population == Option::None{ println!("Элемента с ключом France не существует"); } else{ println!("France : {}", population.unwrap()); } }
Для перебора HashMap можно использовать цикл for:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); countries.insert(String::from("France"), 67); for (key, value) in &countries{ println!("{}: {}", key, value); } }
Цикл пробегает по всем элементам HashMap и помещает в переменную key
ключ, а в переменную value
- значение элемента.
Обратите внимание, что в цикл передается не сам объект HashMap, а ссылка на него, чтобы избежать смену владения объектом HashMap.
Метод contains_key() возвращает true
, если элемент с указанным ключом существует, и false
, если элемент
не существует:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); if countries.contains_key("France"){ println!("{}", countries["France"]); } else{ println!("Элемента France не существует"); } if countries.contains_key("Germany"){ println!("{}", countries["Germany"]); } else{ println!("Элемента Germany не существует"); } }
Данный метод можно применять в качестве альтернативы применения перечисления Option при получении элемента.
В HashMap может существовать только один элемент с определенным ключом. Попытка добавления элемента с ключом, который уже есть в HashMap, приведет к затиранию старого элемента:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); countries.insert(String::from("Germany"), 250); let population = countries["Germany"]; // 250 println!("{}", population); // 250 }
Однако такое поведение может быть нежелательным. Возможно, мы хотим добавить элемент, только если элемента с подобным ключом еще нет в HashMap. В этом случае мы можем проверять наличие ключа с
помощью метода contains_key()
:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); if !countries.contains_key("France"){ countries.insert(String::from("France"), 67); } if !countries.contains_key("Germany"){ countries.insert(String::from("Germany"), 250); } println!("{}", countries["Germany"]); // 82 println!("{}", countries["France"]); // 67 }
Но HashMap также предоставляют специальный API с помощью метода entry(). Этот метод принимает ключ элемента и возвращает перечисление Entry, которое представляет значение элемента. Затем у перечисления Entry вызывается метод or_insert(), в который передается добавляемое значение. Если элемент с ключом уже есть, то метод просто возвращает изменяемую (mutable) ссылку на значение в HashMap. Если элемент отсутствует, то метод добавляет новое значение и также возвращает изменяемую (mutable) ссылку.
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); countries.entry(String::from("France")).or_insert(67); countries.entry(String::from("Germany")).or_insert(250); println!("{}", countries["Germany"]); // 82 println!("{}", countries["France"]); // 67 }
Для удаления одного элемента применяется метод remove(), в который передается ключ удаляемого элемента:
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); countries.insert(String::from("France"), 67); countries.remove("Germany"); // удаляем элемент с ключом Germany }
Для удаления всех элементов используется метод clear():
use std::collections::HashMap; fn main(){ let mut countries = HashMap::new(); countries.insert(String::from("Germany"), 82); countries.insert(String::from("France"), 67); countries.clear(); // очищаем объект HashMap println!("length: {}", countries.len()); // length: 0 }
Это только небольшой перечень методов структуры HashMap. Полный список методов можно найти в документации на странице https://doc.rust-lang.org/std/collections/struct.HashMap.html.