HashMap

Последнее обновление: 28.04.2021

Встроенный тип HashMap<K, V> хранит набор элементов, где каждый элемент имеет ключ и значение. В различных языках программирования есть похожие типы данных, которые могут называться словарями, ассоциативными массивами, хэштаблицами, картами. HashMap типизируется двумя параметрами - K и V. Параметр K устанавливает тип ключей, а параметр V - тип значений элементов.

HashMap располагается в пакете std::collections, поэтому при использовании HashMap нам надо импортировать данный тип с помощью оператора use:

use std::collections::HashMap;

Создание 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);

Передача владения в HashMap

Для значений типов, которые реализуют трейт 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 в квадратных скобках указывается ключ:

переменная_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

Для перебора 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.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850