Пересечение двух слайсов в Go: зачем тут map[int]struct{}?
Допустим, есть два []int: a := []int{1, 2, 3, 4, 5} b := []int{3, 4, 5, 6, 7} Нужно получить элементы, которые есть в обоих слайсах: 3, 4, 5 Первое решение, которое приходит в голову - два вложенных цикла: var result []int
for _, x := range a { for _, y := range b { if x == y { result = append(result, x) break } } } Работает. Но если в каждом слайсе по 100 000 элементов, потенциально мы получаем огромное количество сравнений. Сложность: O(n * m) А теперь map Можно превратить первый слайс в множество: set := make(map[int]struct{})
for _, x := range a { set[x] = struct{}{} } Почему здесь struct{}? У пустой структуры нулевой размер: var x struct{}
fmt.Println(unsafe.Sizeof(x)) // 0 Нам не нужно хранить какое-то значение. Нас интересует только один вопрос: есть такой элемент или нет? Поэтому: map[int]struct{} удобно использовать как простой set. При этом сам map, конечно, память занимает - хранятся ключи и внутренние структуры map. Речь именно о том, что значение struct{} имеет нулевой размер. Теперь проверяем второй слайс: var result []int
for _, x := range b { if _, ok := set[x]; ok { result = append(result, x) } } Полностью: func intersection(a, b []int) []int { set := make(map[int]struct{}, len(a))
for _, x := range a { set[x] = struct{}{} }
var result []int
for _, x := range b { if _, ok := set[x]; ok { result = append(result, x) } }
return result } Логика получается такая: a ↓ map
1 → {} 2 → {} 3 → {} 4 → {} 5 → {}
↓
проверяем b
3 → есть 4 → есть 5 → есть 6 → нет 7 → нет Средняя стоимость поиска ключа в map - O(1). Поэтому общая сложность: O(n + m) вместо: O(n * m) Но есть нюанс с дубликатами Например: a := []int{1, 2, 2, 3} b := []int{2, 2, 4} Если просто проверять каждый элемент b, результат будет: 2, 2 Потому что map используется только для проверки наличия элемента. Если нужны уникальные пересечения, можно использовать ещё один set: resultSet := make(map[int]struct{})
for _, x := range b { if _, ok := set[x]; ok { resultSet[x] = struct{}{} } } На мой взгляд, это хороший пример того, как в Go один и тот же инструмент можно использовать не только как обычный map[key]value. map[int]struct{} можно мысленно воспринимать как: set of integers И struct{} здесь выбран не случайно: нам не нужно хранить значение, нам нужно только зафиксировать факт существования ключа. Такие маленькие приёмы довольно часто встречаются в реальном Go-коде.