🦀 Rust

Множества (Set) в Rust 1.75: подробное руководство

Множества (HashSet) в Rust

Что такое множество и зачем оно нужно?

Представь, что у тебя есть коробка, в которую можно класть только уникальные предметы. Если ты попробуешь положить туда два одинаковых яблока, второе яблоко просто не поместится — оно уже есть! В программировании такая «коробка» называется множеством (Set).

Множество — это коллекция, которая хранит только уникальные элементы. В Rust для этого используется тип HashSet. Он идеально подходит, когда нужно:

  • Убрать повторяющиеся элементы из списка
  • Проверить, есть ли элемент в коллекции (очень быстро)
  • Найти общие элементы между двумя списками
  • Объединить два списка без дубликатов

Как создать и использовать множество

Чтобы использовать HashSet, его нужно сначала подключить в программе. Добавь в самое начало файла:

use std::collections::HashSet;

Теперь рассмотрим основные операции на примере простой программы:

use std::collections::HashSet;

fn main() {
    // Создаём пустое множество
    let mut fruits: HashSet<String> = HashSet::new();
    
    // Добавляем элементы
    fruits.insert(String::from("яблоко"));
    fruits.insert(String::from("банан"));
    fruits.insert(String::from("апельсин"));
    fruits.insert(String::from("яблоко")); // Это не добавится!
    
    println!("Фрукты в корзине: {:?}", fruits);
    
    // Проверяем, есть ли элемент
    if fruits.contains("банан") {
        println!("Банан есть в корзине!");
    }
    
    // Удаляем элемент
    fruits.remove("апельсин");
    println!("После удаления: {:?}", fruits);
    
    // Узнаём количество элементов
    println!("Всего фруктов: {}", fruits.len());
}

Как превратить список в множество

Часто множества получают из обычных списков (векторов). Это делается с помощью метода into_iter().collect():

use std::collections::HashSet;

fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();
    
    // Разделяем строку по запятым и собираем в вектор
    let items: Vec<&str> = input.trim().split(',').collect();
    
    // Превращаем вектор в множество (дубликаты исчезнут)
    let unique_items: HashSet<&str> = items.into_iter().collect();
    
    println!("Уникальные элементы: {:?}", unique_items);
}

Разбор типичных задач

Задача 1: Ингредиенты пиццы

Пользователь вводит ингредиенты через запятую. Нужно оставить только уникальные.

use std::collections::HashSet;
use std::io;

fn main() {
    println!("Введите ингредиенты через запятую:");
    
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    
    // Убираем лишние пробелы и делим на части
    let ingredients: Vec<&str> = input.trim()
        .split(',')
        .map(|s| s.trim()) // Убираем пробелы вокруг
        .collect();
    
    let unique: HashSet<&str> = ingredients.into_iter().collect();
    
    println!("Уникальные ингредиенты для пиццы:");
    for item in &unique {
        println!("- {}", item);
    }
}

Задача 2: Объединение списков покупок

Есть два списка покупок, нужно объединить их без повторов.

use std::collections::HashSet;

fn main() {
    let list1 = vec!["молоко", "хлеб", "сыр", "яблоки"];
    let list2 = vec!["сыр", "колбаса", "хлеб", "сок"];
    
    // Превращаем оба списка в множества
    let set1: HashSet<&str> = list1.into_iter().collect();
    let set2: HashSet<&str> = list2.into_iter().collect();
    
    // Объединяем (операция |)
    let union: HashSet<&str> = set1.union(&set2).cloned().collect();
    
    println!("Итоговый список покупок: {:?}", union);
}

Задача 3: Поиск общих товаров

Есть список обязательных товаров и список текущего наличия. Нужно найти, что уже есть.

use std::collections::HashSet;

fn main() {
    let must_have = vec!["мука", "сахар", "соль", "масло", "яйца"];
    let in_stock = vec!["сахар", "соль", "молоко"];
    
    let must_set: HashSet<&str> = must_have.into_iter().collect();
    let stock_set: HashSet<&str> = in_stock.into_iter().collect();
    
    // Пересечение (операция &)
    let available: HashSet<&str> = must_set.intersection(&stock_set).cloned().collect();
    
    println!("Уже есть на складе: {:?}", available);
}

Частые ошибки новичков

  • Забыть подключить HashSet: Всегда пиши use std::collections::HashSet; в начале программы.
  • Пытаться получить элемент по индексу: В множестве нет индексов! Нельзя написать set[0]. Используй contains для проверки.
  • Путать с вектором: Вектор хранит элементы в порядке добавления и может содержать дубликаты. Множество не хранит порядок и не допускает дубликатов.
  • Забыть про mut: Если планируешь изменять множество (добавлять или удалять элементы), обязательно пиши let mut set.
  • Сравнение строк: Rust чувствителен к регистру. "Яблоко" и "яблоко" — разные элементы.

Итоговый пример: Записная книжка контактов

Создадим программу, которая хранит имена контактов. Если пользователь пытается добавить уже существующее имя, программа сообщает об этом.

use std::collections::HashSet;
use std::io;

fn main() {
    let mut contacts: HashSet<String> = HashSet::new();
    
    loop {
        println!("\n--- Записная книжка ---");
        println!("1. Добавить контакт");
        println!("2. Показать все контакты");
        println!("3. Выйти");
        println!("Выберите действие (1-3):");
        
        let mut choice = String::new();
        io::stdin().read_line(&mut choice).unwrap();
        
        match choice.trim() {
            "1" => {
                println!("Введите имя контакта:");
                let mut name = String::new();
                io::stdin().read_line(&mut name).unwrap();
                let name = name.trim().to_string();
                
                if contacts.insert(name.clone()) {
                    println!("Контакт '{}' добавлен!", name);
                } else {
                    println!("Контакт '{}' уже существует!", name);
                }
            }
            "2" => {
                if contacts.is_empty() {
                    println!("Записная книжка пуста.");
                } else {
                    println!("Ваши контакты:");
                    for contact in &contacts {
                        println!("- {}", contact);
                    }
                    println!("Всего контактов: {}", contacts.len());
                }
            }
            "3" => {
                println!("До свидания!");
                break;
            }
            _ => {
                println!("Неверный выбор, попробуйте снова.");
            }
        }
    }
}

Эта программа использует все основные возможности HashSet: добавление (с проверкой на уникальность), проверку пустоты, подсчёт элементов и перебор. Попробуй запустить её и добавить несколько контактов с одинаковыми именами — второе имя не добавится!