HashMap

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

Встроенный тип 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 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}
}

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

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

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

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

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