🐹 Go

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

Множества (Set) в Go 1.21

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

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

Зачем это нужно? Например:

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

В Go 1.21 нет встроенного типа "множество", но мы можем легко его создать с помощью map!

Как создать множество в Go?

Мы будем использовать map[string]struct{}. Почему struct{}? Потому что это пустая структура, которая не занимает памяти — идеально для хранения только ключей!

// Создаём пустое множество
contacts := make(map[string]struct{})

// Добавляем элементы
contacts["Анна"] = struct{}{}
contacts["Борис"] = struct{}{}
contacts["Анна"] = struct{}{} // Повторно — ничего не изменится

// Проверяем, есть ли элемент
if _, exists := contacts["Анна"]; exists {
    fmt.Println("Анна есть в контактах!")
}

// Удаляем элемент
delete(contacts, "Борис")

// Количество элементов
fmt.Println(len(contacts)) // Выведет: 1

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

1. Создание и добавление элементов

package main

import "fmt"

func main() {
    // Создаём множество для ингредиентов пиццы
    ingredients := make(map[string]struct{})
    
    // Добавляем ингредиенты
    ingredients["сыр"] = struct{}{}
    ingredients["томаты"] = struct{}{}
    ingredients["сыр"] = struct{}{} // Повторно не добавится
    
    fmt.Println("Ингредиенты:", ingredients) // map[сыр:{} томаты:{}]
    fmt.Println("Количество:", len(ingredients)) // 2
}

2. Проверка наличия элемента

package main

import "fmt"

func main() {
    products := map[string]struct{}{
        "молоко": {},
        "хлеб":   {},
        "яйца":   {},
    }
    
    var item string
    fmt.Print("Введите товар для проверки: ")
    fmt.Scan(&item)
    
    if _, exists := products[item]; exists {
        fmt.Println("Товар есть в наличии!")
    } else {
        fmt.Println("Товара нет в наличии.")
    }
}

3. Удаление элементов

package main

import "fmt"

func main() {
    shoppingList := map[string]struct{}{
        "молоко": {},
        "хлеб":   {},
        "яйца":   {},
        "сыр":    {},
    }
    
    fmt.Println("Список покупок:", shoppingList)
    
    // Удаляем ненужный товар
    delete(shoppingList, "молоко")
    
    fmt.Println("После удаления:", shoppingList)
}

4. Объединение множеств

package main

import "fmt"

func main() {
    // Список покупок хозяина
    hostList := map[string]struct{}{
        "сок":     {},
        "чипсы":   {},
        "пицца":   {},
    }
    
    // Что предложили гости
    guestList := map[string]struct{}{
        "сок":     {},
        "торт":    {},
        "лимонад": {},
    }
    
    // Объединяем — добавляем всё из guestList в hostList
    for item := range guestList {
        hostList[item] = struct{}{}
    }
    
    fmt.Println("Финальный список покупок:")
    for item := range hostList {
        fmt.Println("-", item)
    }
}

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

Задача: Ингредиенты пиццы (уникальные)

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Print("Введите ингредиенты через запятую: ")
    var input string
    fmt.Scan(&input)
    
    // Разбиваем строку на части
    items := strings.Split(input, ",")
    
    // Создаём множество для уникальных ингредиентов
    uniqueIngredients := make(map[string]struct{})
    
    for _, item := range items {
        // Убираем лишние пробелы и добавляем
        cleanItem := strings.TrimSpace(item)
        if cleanItem != "" {
            uniqueIngredients[cleanItem] = struct{}{}
        }
    }
    
    fmt.Println("Уникальные ингредиенты:")
    for ingredient := range uniqueIngredients {
        fmt.Println("-", ingredient)
    }
}

Ввод: сыр, томаты, сыр, грибы, томаты

Вывод: сыр, томаты, грибы

Задача: Поиск отсутствующих товаров

package main

import "fmt"

func main() {
    // Товары, которые всегда должны быть
    required := map[string]struct{}{
        "молоко": {},
        "хлеб":   {},
        "яйца":   {},
        "сыр":    {},
    }
    
    // Спрашиваем, что есть
    fmt.Println("Какие товары есть? (вводите по одному, 'стоп' для завершения)")
    available := make(map[string]struct{})
    
    for {
        var item string
        fmt.Print("Товар: ")
        fmt.Scan(&item)
        
        if item == "стоп" {
            break
        }
        available[item] = struct{}{}
    }
    
    // Ищем, чего не хватает
    fmt.Println("Нужно докупить:")
    for item := range required {
        if _, exists := available[item]; !exists {
            fmt.Println("-", item)
        }
    }
}

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

  • Забывают проверить существование элемента — всегда используй if _, exists := set[key]; exists { ... }
  • Пытаются получить значение по ключу — в множестве нет значений, только ключи. Используй проверку существования.
  • Путают map[string]bool и map[string]struct{} — struct{}{} занимает 0 байт, а bool — 1 байт. Для множеств лучше struct{}{}.
  • Не очищают пробелы — при вводе через запятую остаются пробелы. Всегда используй strings.TrimSpace().

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

package main

import "fmt"

func main() {
    // Множество для хранения уникальных имён
    contacts := make(map[string]struct{})
    
    fmt.Println("=== Записная книжка контактов ===")
    fmt.Println("Команды: добавить [имя], удалить [имя], показать, выход")
    
    for {
        fmt.Print("\n> ")
        var command, name string
        fmt.Scan(&command)
        
        switch command {
        case "добавить":
            fmt.Scan(&name)
            contacts[name] = struct{}{}
            fmt.Println("Контакт", name, "добавлен!")
            
        case "удалить":
            fmt.Scan(&name)
            delete(contacts, name)
            fmt.Println("Контакт", name, "удалён!")
            
        case "показать":
            if len(contacts) == 0 {
                fmt.Println("Контактов пока нет.")
            } else {
                fmt.Println("Ваши контакты:")
                for contact := range contacts {
                    fmt.Println("-", contact)
                }
            }
            
        case "выход":
            fmt.Println("До свидания!")
            return
            
        default:
            fmt.Println("Неизвестная команда. Попробуйте: добавить, удалить, показать, выход")
        }
    }
}

Пример работы:

> добавить Анна
Контакт Анна добавлен!
> добавить Борис
Контакт Борис добавлен!
> добавить Анна
Контакт Анна добавлен!  // Повторно не добавится
> показать
Ваши контакты:
- Анна
- Борис
> удалить Анна
Контакт Анна удалён!
> показать
Ваши контакты:
- Борис
> выход
До свидания!

Теперь ты знаешь, как использовать множества в Go! Они помогают хранить только уникальные значения и быстро проверять, есть ли элемент в коллекции. Используй map[string]struct{}{} для создания множества и не забывай проверять существование ключей через if _, exists := ....