sort)
Сортировка с сохранением ключей (аналог asort)
Пользовательская сортировка (аналог usort)
Удаление элемента (аналог unset)
Сравнение производительности коллекций
Примеры: обработка списков и словарей
Упражнение: Реализовать поиск в HashMap
Добро пожаловать в девятый раздел нашего курса по Rust! Сегодня мы погрузимся в мир коллекций — структур данных, которые позволяют хранить и обрабатывать наборы элементов. Мы разберём основные типы коллекций (Vec, String, HashMap, HashSet), изучим их методы, операции, итераторы, сравним их производительность и закрепим знания практическими примерами и упражнением. Эта лекция рассчитана как на новичков, так и на тех, кто хочет углубить свои знания, поэтому я постараюсь объяснить всё максимально подробно, шаг за шагом.
Rust предоставляет набор коллекций в стандартной библиотеке (std::collections), которые решают типичные задачи работы с данными. Коллекции — это не примитивные типы, а структуры, которые используют динамическую память (heap) для хранения данных. Давайте разберём основные из них.
Vec<T> — Динамический массивVec (сокращение от "vector") — это изменяемый массив, который может расти или уменьшаться во время выполнения программы. Это одна из самых часто используемых коллекций в Rust.
T в непрерывной области памяти.[T; N] с фиксированным размером).String — СтрокаString — это изменяемая строка, которая хранит данные в формате UTF-8. Она похожа на Vec<u8>, но с дополнительной гарантией валидности UTF-8.
Vec.&str — это неизменяемый "срез" строки, а String — полноценный владеющий тип.HashMap<K, V> — Ассоциативный массивHashMap — это структура "ключ-значение", где каждому ключу типа K соответствует значение типа V. Работает на основе хэширования.
HashSet<T> — МножествоHashSet — это коллекция уникальных элементов типа T, тоже основанная на хэшировании.
Теперь разберём, как работать с этими коллекциями. Я приведу примеры кода, чтобы вы могли сразу попробовать их в действии.
Vec<T>Создание, добавление и доступ к элементам:
fn main() {
// Создаём пустой Vec
let mut numbers: Vec<i32> = Vec::new();
// Добавляем элементы
numbers.push(1);
numbers.push(2);
numbers.push(3);
// Доступ по индексу
println!("Первый элемент: {}", numbers[0]);
// Длина Vec
println!("Длина: {}", numbers.len());
// Удаляем последний элемент
let last = numbers.pop();
println!("Удалённый элемент: {:?}", last); // Some(3)
}
push добавляет элемент в конец (O(1) в среднем, но может быть O(n), если нужно перевыделить память).pop удаляет и возвращает последний элемент (O(1)).numbers[0]) паникует, если индекс вне диапазона. Безопасная альтернатива — метод get (возвращает Option).StringСоздание и модификация строки:
fn main() {
// Создаём пустую строку
let mut s = String::new();
// Добавляем текст
s.push_str("Привет, ");
s.push('R'); // Добавляет один символ
s.push_str("ust!");
println!("Строка: {}", s); // Привет, Rust!
// Очистка строки
s.clear();
println!("После очистки: '{}'", s);
}
push_str добавляет строковый срез (&str).push добавляет один символ (char).clear очищает строку, но сохраняет выделенную память.HashMap<K, V>Работа с парами ключ-значение:
use std::collections::HashMap;
fn main() {
// Создаём пустой HashMap
let mut scores = HashMap::new();
// Добавляем элементы
scores.insert("Алиса", 10);
scores.insert("Боб", 15);
// Получаем значение
if let Some(score) = scores.get("Алиса") {
println!("Очки Алисы: {}", score); // 10
}
// Удаляем элемент
scores.remove("Боб");
}
insert добавляет или обновляет пару ключ-значение.get возвращает Option<&V>.remove удаляет элемент по ключу.HashSet<T>Работа с уникальными элементами:
use std::collections::HashSet;
fn main() {
let mut set = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(1); // Дубликат, не добавится
println!("Размер множества: {}", set.len()); // 2
// Проверяем наличие
if set.contains(&2) {
println!("Множество содержит 2");
}
}
insert добавляет элемент, если его ещё нет.contains проверяет наличие элемента.iter, into_iter, iter_mutИтераторы — мощный инструмент для работы с коллекциями. Они позволяют обходить элементы, не заботясь о деталях реализации. В Rust есть три основных вида итераторов:
iter — Заимствование элементовВозвращает итератор по ссылкам (&T):
&T) на элементы коллекции.fn main() {
let numbers = vec![1, 2, 3];
for num in numbers.iter() {
println!("Элемент: {}", num);
}
}
Используется для чтения данных без изменения коллекции.
into_iter — Перемещение элементовПотребляет коллекцию и возвращает итератор по значениям (T):
T), а не ссылки. После этого коллекция "исчезает" (перемещается в итератор).
Когда ты пишешь for x in коллекция, Rust автоматически вызывает into_iter(), потому что это "по умолчанию" забирает коллекцию. Если хочешь оставить коллекцию, явно используй for x in коллекция.iter().
fn main() {
let numbers = vec![1, 2, 3];
for num in numbers.into_iter() {
println!("Число: {}", num);
}
// numbers больше недоступен
}
После этого коллекция уничтожается (перемещается в итератор).
iter_mut — Изменение элементовВозвращает итератор по изменяемым ссылкам (&mut T):
&mut T) на элементы коллекции.
iter, но ты можешь менять её содержимое через эти ссылки.
fn main() {
let mut numbers = vec![1, 2, 3];
for num in numbers.iter_mut() {
*num += 10; // Изменяем элементы
}
println!("Изменённый Vec: {:?}", numbers); // [11, 12, 13]
}
Подходит для модификации коллекции на месте.
Таблица с отличиями между iter, into_iter и iter_mut в Rust простыми словами:
| Характеристика | iter |
into_iter |
iter_mut |
|---|---|---|---|
| Что возвращает | Ссылки (<T>) |
Сами значения (T) |
Изменяемые ссылки ( <mut T>) |
| Можно ли менять элементы | Нет, только смотреть | Да, но коллекция уже твоя | Да, через ссылки |
| Что с коллекцией | Остаётся живой | "Исчезает" (перемещается) | Остаётся живой |
| Тип итератора | Итератор по ссылкам | Итератор по значениям | Итератор по изменяемым ссылкам |
| Когда использовать | Хочу посмотреть элементы | Хочу забрать элементы | Хочу изменить элементы на месте |
Требуется ли mut для коллекции |
Нет | Нет (но коллекция уходит) | Да, коллекция должна быть mut |
| Пример кода | for x in vec.iter() |
for x in vec.into_iter() |
for x in vec.iter_mut() |
| Пример результата | x — ссылка, vec жив |
x — значение, vec мёртв |
x — изменяемая ссылка, vec жив |
let mut numbers = vec![1, 2, 3];
// iter
for num in numbers.iter() {
println!("Только смотрю: {}", num); // <i32>
}
println!("numbers после iter: {:?}", numbers); // [1, 2, 3]
// into_iter
for num in numbers.into_iter() {
println!("Забираю: {}", num); // i32
}
// println!("{:?}", numbers); // Ошибка, numbers перемещён
// iter_mut
let mut numbers = vec![1, 2, 3];
for num in numbers.iter_mut() {
*num += 10; // Меняю через <mut i32>
}
println!("numbers после iter_mut: {:?}", numbers); // [11, 12, 13]
sort)[T; N]) неизменяемы по размеру, а для динамических коллекций используется Vec<T>. Для сортировки есть метод .sort():
let mut numbers = vec![3, 1, 4, 1, 5];
numbers.sort();
println!("{:?}", numbers); // [1, 1, 3, 4, 5]
Поведение: сортировка идёт по возрастанию, но ключи в Vec не существуют (это просто последовательность элементов).
В Rust нужно объявить mut, чтобы разрешить изменение вектора и требует, чтобы тип элементов реализовал трейт Ord (для сравнения). Для примитивных типов (i32, f64, String) это уже сделано.
asort)Для словарей в Rust используется HashMap или BTreeMap (из std::collections). HashMap не поддерживает встроенную сортировку, так как порядок ключей не гарантирован, но BTreeMap хранит ключи в отсортированном порядке автоматически. Однако если нужно отсортировать по значениям придётся:
HashMap в вектор пар (ключ, значение).Пример:
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::from([
("b", 3),
("a", 1),
("c", 2),
]);
let mut sorted: Vec<(&str, i32)> = map.into_iter().collect();
sorted.sort_by(|a, b| a.1.cmp(&b.1)); // сортировка по значению
println!("{:?}", sorted); // [("a", 1), ("c", 2), ("b", 3)]
sorted теперь Vec<(&str, i32)>, а не HashMap, если нужно сохранить как словарь, потребуется дополнительный шаг.
В Rust нет прямого "сортировать на месте с ключами". Нужно создавать новый вектор или вручную преобразовывать данные.
usort)В Rust метод .sort_by() принимает замыкание (closure) для пользовательской сортировки:
let mut numbers = vec![3, 1, 4, 1, 5];
numbers.sort_by(|a, b| b.cmp(a)); // по убыванию
println!("{:?}", numbers); // [5, 4, 3, 1, 1]
|a, b| — это замыканиеb.cmp(a) возвращает порядок: Ordering::Less, Equal или Greater. Инверсия (b.cmp(a) вместо a.cmp(b)) даёт убывающий порядок.Альтернатива: .sort_by_key() для сортировки по вычисляемому ключу:
let mut numbers = vec![3, 1, 4, 1, 5];
numbers.sort_by_key(|&x| -x); // по убыванию через отрицание
println!("{:?}", numbers); // [5, 4, 3, 1, 1]
Rust требует mut и типобезопасности, но замыкания довольно гибкие и мощные
unset)В Rust для Vec используется метод .remove(index):
let mut numbers = vec![1, 2, 3, 4];
numbers.remove(1); // удаляет элемент с индексом 1
println!("{:?}", numbers); // [1, 3, 4]
.remove() сдвигает все последующие элементы, что может быть дорого для больших векторов (O(n)).mut, так как изменяет вектор.Для словарей (HashMap):
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::from([
("a", 1),
("b", 2),
("c", 3),
]);
map.remove("b"); // удаляет по ключу "b"
println!("{:?}", map); // {"a": 1, "c": 3}
Альтернатива: Если не нужно сдвигать элементы, можно использовать .swap_remove(index) (быстрее, O(1), но меняет порядок):
let mut numbers = vec![1, 2, 3, 4];
numbers.swap_remove(1); // заменяет на последний элемент и удаляет его
println!("{:?}", numbers); // [1, 4, 3]
Каждая коллекция оптимизирована под свои задачи. Вот краткое сравнение:
| Коллекция | Доступ по индексу | immВставка | Поиск | Память |
|---|---|---|---|---|
Vec |
O(1) | O(1) аморт.* | O(n) | Непрерывная |
String |
O(1) (по байтам) | O(1) аморт.* | O(n) | Непрерывная |
HashMap |
— | O(1) в среднем | O(1) в среднем | Разбросанная |
HashSet |
— | O(1) в среднем | O(1) в среднем | Разбросанная |
*Амортизированная O(1) — в среднем быстрая, но может быть O(n) при перевыделении памяти.
Vec хорош для последовательного доступа, но поиск медленный.HashMap и HashSet быстры для поиска, но требуют больше памяти из-за хэш-таблицы.Vec)Допустим, нужно отфильтровать чётные числа:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let even: Vec<i32> = numbers.into_iter()
.filter(|&x| x % 2 == 0)
.collect();
println!("Чётные числа: {:?}", even); // [2, 4, 6]
}
Метод collect собирает итератор обратно в коллекцию (например, Vec).
HashMap)Подсчитаем, сколько раз каждое слово встречается в тексте:
use std::collections::HashMap;
fn main() {
let text = "hello world hello rust world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
*word_count.entry(word).or_insert(0) += 1;
}
println!("Подсчёт слов: {:?}", word_count);
// {"hello": 2, "world": 2, "rust": 1}
}
Метод entry позволяет избежать проверки contains_key вручную.
HashMapНапишите программу, которая:
HashMap с именами (строки) и возрастами (целые числа).Вот полный код с пояснениями:
use std::collections::HashMap;
use std::io;
fn main() {
// 1. Создаём HashMap
let mut people = HashMap::new();
people.insert(String::from("Алиса"), 25);
people.insert(String::from("Боб"), 30);
people.insert(String::from("Чарли"), 35);
// 2. Запрашиваем имя
println!("Введите имя для поиска:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Ошибка чтения ввода");
let name = input.trim(); // Убираем \n
// 3. Ищем и выводим результат
match people.get(name) {
Some(age) => println!("Возраст {}: {}", name, age),
None => println!("{} не найдено", name),
}
}
io::stdin().read_line читает строку от пользователя.trim() убирает лишние символы (например, перенос строки).get возвращает Option, который мы обрабатываем через match.Попробуйте запустить код и протестировать с именами "Алиса", "Боб" и "Ева" (последнего нет в словаре).
Vec для упорядоченных данных, HashMap для быстрого поиска, HashSet для уникальности.map, filter и collect для лаконичного кода.Vec::with_capacity.Option и match вместо паники при доступе к данным.Коллекции — это основа работы с данными в Rust. Вы изучили их типы, методы, итераторы и даже написали небольшую программу. Теперь вы готовы применять их в своих проектах!
Экспериментируйте с кодом!. Удачи в изучении Rust!