Как проверить, содержит ли срез (slice) в Go определенный элемент? Аналог contains() для среза
Правильный ответ: в Go нет встроенного contains(), но это идиоматично — писать свою функцию
Вы правы: в стандартной библиотеке Go нет универсального метода contains() для срезов. Это осознанное решение разработчиков языка. В Go предпочитают явные циклы, чтобы код был простым и предсказуемым. Однако это не значит, что вам нужно писать цикл каждый раз вручную. Идиоматическое решение — написать небольшую вспомогательную функцию один раз и переиспользовать её.
Базовое решение для строк
Самый простой и понятный способ — это цикл for range:
package main
import "fmt"
// contains проверяет, содержит ли срез строк элемент target
func contains(slice []string, target string) bool {
for _, s := range slice {
if s == target {
return true
}
}
return false
}
func main() {
fruits := []string{"apple", "banana", "cherry"}
fmt.Println(contains(fruits, "banana")) // true
fmt.Println(contains(fruits, "grape")) // false
}
Универсальное решение с дженериками (Go 1.18+)
Начиная с версии Go 1.18, вы можете использовать дженерики, чтобы написать одну функцию для любого типа, поддерживающего сравнение:
package main
import "fmt"
// containsGeneric работает для любого comparable типа (string, int, float64 и т.д.)
func containsGeneric[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
func main() {
strings := []string{"a", "b", "c"}
ints := []int{1, 2, 3}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(containsGeneric(strings, "b")) // true
fmt.Println(containsGeneric(ints, 4)) // false
fmt.Println(containsGeneric(floats, 2.2)) // true
}
Если вам нужно часто — используйте map
Если вы проверяете наличие элементов очень часто (миллионы раз), и порядок элементов не важен, конвертируйте срез в map. Поиск по ключу в map — O(1), тогда как в срезе — O(n):
package main
import "fmt"
func main() {
fruits := []string{"apple", "banana", "cherry"}
fruitSet := make(map[string]struct{}, len(fruits))
for _, f := range fruits {
fruitSet[f] = struct{}{}
}
// Теперь проверка мгновенная
_, exists := fruitSet["banana"]
fmt.Println(exists) // true
}
Важные замечания
- Производительность: Для маленьких срезов (до 100 элементов) разница между циклом и map незаметна. Для больших срезов map быстрее, но требует больше памяти.
- Сложные типы: Если вы храните в срезе структуры, дженерики не помогут — нужно писать функцию с кастомной логикой сравнения.
- Сторонние библиотеки: Не стоит подключать целую библиотеку (например,
samber/lo) только ради одной функции contains. Лучше написать 3 строки кода.
В итоге: да, в Go нет contains() встроенного, но написать свою функцию — это нормальная практика. Go поощряет явность и простоту, даже если это означает чуть больше кода.
Альтернативный подход: использование пакета slices (Go 1.21+)
Начиная с версии Go 1.21, в стандартной библиотеке появился пакет slices, который включает функцию slices.Contains(). Это самое современное и рекомендуемое решение, если вы используете Go 1.21 или новее:
package main
import (
"fmt"
"slices"
)
func main() {
fruits := []string{"apple", "banana", "cherry"}
fmt.Println(slices.Contains(fruits, "banana")) // true
fmt.Println(slices.Contains(fruits, "grape")) // false
numbers := []int{10, 20, 30}
fmt.Println(slices.Contains(numbers, 25)) // false
}
Плюсы этого подхода:
- Не нужно писать свою функцию
- Работает для любого comparable типа (строки, числа, булевы значения, указатели)
- Оптимизирован разработчиками Go
Минусы:
- Не работает для структур (нужна своя логика сравнения)
- Требует Go 1.21+ (если вы работаете на старой версии, используйте дженерики из первого ответа)
Если ваш проект использует актуальную версию Go, просто используйте slices.Contains() — это самый простой и правильный путь.