🔷 C#

Множества (Set) в C# (.NET 6): подробное руководство

Множества (Set) в C#: Уникальные коллекции без повторов

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

Представь, что ты собираешь коллекцию наклеек. Ты хочешь, чтобы каждая наклейка была только одна — никаких дубликатов. Именно так работает множество (Set) в программировании.

Множество — это коллекция, которая хранит только уникальные элементы. Если ты попробуешь добавить в него уже существующий элемент, он просто не добавится. Это очень удобно, когда нужно:

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

В C# для работы с множествами используется тип HashSet<T> (где T — это тип элементов, например, string или int).

Основной синтаксис

Создать пустое множество:

HashSet<string> продукты = new HashSet<string>();

Создать множество с начальными элементами:

HashSet<string> фрукты = new HashSet<string>() { "яблоко", "банан", "апельсин" };

Добавить элемент:

продукты.Add("молоко");

Удалить элемент:

продукты.Remove("молоко");

Проверить, есть ли элемент:

if (продукты.Contains("молоко"))
{
    Console.WriteLine("Молоко есть в списке!");
}

Узнать количество элементов:

int количество = продукты.Count;

Перебрать все элементы (каждый встретится только один раз):

foreach (string продукт in продукты)
{
    Console.WriteLine(продукт);
}

Главные операции с множествами

Множества поддерживают математические операции, которые очень полезны на практике:

Объединение (UnionWith) — добавляет все элементы из другого множества или списка:

HashSet<string> основные = new HashSet<string>() { "хлеб", "молоко" };
HashSet<string> дополнительные = new HashSet<string>() { "молоко", "яйца" };
основные.UnionWith(дополнительные);
// Теперь основные содержит: "хлеб", "молоко", "яйца" (молоко не продублировалось!)

Пересечение (IntersectWith) — оставляет только те элементы, которые есть в обоих множествах:

HashSet<string> моиИнгредиенты = new HashSet<string>() { "сыр", "помидор", "базилик" };
HashSet<string> рецептПиццы = new HashSet<string>() { "сыр", "колбаса", "помидор" };
моиИнгредиенты.IntersectWith(рецептПиццы);
// Теперь моиИнгредиенты содержит: "сыр", "помидор"

Разность (ExceptWith) — удаляет элементы, которые есть в другом множестве:

HashSet<string> списокПокупок = new HashSet<string>() { "хлеб", "молоко", "яйца", "масло" };
HashSet<string> ужеКупил = new HashSet<string>() { "молоко", "яйца" };
списокПокупок.ExceptWith(ужеКупил);
// Теперь списокПокупок содержит: "хлеб", "масло" (то, что ещё нужно купить)

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

Задача: Уникальные ингредиенты пиццы

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

Console.WriteLine("Введите ингредиенты через запятую:");
string ввод = Console.ReadLine();

// Разделяем строку по запятым
string[] ингредиенты = ввод.Split(',');

// Создаём множество — оно автоматически уберёт дубликаты
HashSet<string> уникальные = new HashSet<string>();

foreach (string ингредиент in ингредиенты)
{
    // Убираем лишние пробелы и добавляем
    уникальные.Add(ингредиент.Trim());
}

Console.WriteLine("Уникальные ингредиенты:");
foreach (string ингредиент in уникальные)
{
    Console.WriteLine("- " + ингредиент);
}

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

Console.WriteLine("Введите ваш список покупок (через запятую):");
string вашВвод = Console.ReadLine();
string[] вашСписок = вашВвод.Split(',');

Console.WriteLine("Введите список от гостей (через запятую):");
string гостиВвод = Console.ReadLine();
string[] гостиСписок = гостиВвод.Split(',');

// Создаём множество из первого списка
HashSet<string> общийСписок = new HashSet<string>();
foreach (string товар in вашСписок)
{
    общийСписок.Add(товар.Trim());
}

// Добавляем товары от гостей (дубликаты не добавятся)
foreach (string товар in гостиСписок)
{
    общийСписок.Add(товар.Trim());
}

Console.WriteLine("Итоговый список покупок (без повторов):");
foreach (string товар in общийСписок)
{
    Console.WriteLine("- " + товар);
}

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

Ошибка 1: Пытаться получить элемент по индексу, как в массиве.

// ❌ Так нельзя
string элемент = моёМножество[0]; // Ошибка!

// ✅ Нужно перебирать через foreach или проверять Contains

Ошибка 2: Забывать, что дубликаты не добавляются, и считать, что Add вернёт true.

HashSet<string> set = new HashSet<string>();
bool добавилось = set.Add("яблоко"); // true
bool добавилось2 = set.Add("яблоко"); // false — яблоко уже есть
// Метод Add возвращает true, если элемент действительно добавился

Ошибка 3: Не очищать строки от пробелов перед добавлением.

// Если пользователь ввёл: "сыр, помидор, сыр"
// Без Trim() получится: " сыр", " помидор", " сыр" — три разных элемента!
// С Trim(): "сыр", "помидор", "сыр" — дубликаты уберутся

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

Создадим программу, которая хранит имена контактов (только уникальные) и их номера телефонов.

// Хранилище контактов: имя → множество телефонов
Dictionary<string, HashSet<string>> записнаяКнижка = new Dictionary<string, HashSet<string>>();

while (true)
{
    Console.WriteLine("\n1. Добавить контакт");
    Console.WriteLine("2. Показать все контакты");
    Console.WriteLine("3. Выйти");
    Console.Write("Выберите действие: ");
    string выбор = Console.ReadLine();

    if (выбор == "1")
    {
        Console.Write("Введите имя контакта: ");
        string имя = Console.ReadLine().Trim();

        Console.Write("Введите номер телефона: ");
        string телефон = Console.ReadLine().Trim();

        // Если контакта ещё нет — создаём новое множество для телефонов
        if (!записнаяКнижка.ContainsKey(имя))
        {
            записнаяКнижка[имя] = new HashSet<string>();
        }

        // Добавляем телефон (если такой уже есть — не добавится)
        if (записнаяКнижка[имя].Add(телефон))
        {
            Console.WriteLine($"Телефон {телефон} добавлен для контакта {имя}");
        }
        else
        {
            Console.WriteLine($"Телефон {телефон} уже есть у контакта {имя}");
        }
    }
    else if (выбор == "2")
    {
        foreach (var контакт in записнаяКнижка)
        {
            Console.WriteLine($"\n{контакт.Key}:");
            foreach (string телефон in контакт.Value)
            {
                Console.WriteLine($"  - {телефон}");
            }
        }
    }
    else if (выбор == "3")
    {
        break;
    }
}

Console.WriteLine("До свидания!");

В этом примере HashSet<string> гарантирует, что у одного контакта не будет дублирующихся номеров телефонов. Попробуй добавить один и тот же номер дважды — второй раз он не добавится!

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

  • Нужно хранить только уникальные значения
  • Нужно быстро проверить, есть ли элемент в коллекции
  • Нужно объединять или сравнивать коллекции
  • Порядок элементов не важен

Множества работают очень быстро — в тысячи раз быстрее, чем проверка в обычном списке, если элементов много. Это делает их идеальным выбором для задач, где важна скорость поиска.