Contains в Go для слайсов: как проверить и не сойти с ума

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

«А где же contains?» — первая боль новичка в Go

Представьте: вы пишете на Go, у вас есть слайс строк ["python", "go", "rust", "javascript"], и вам нужно проверить, есть ли в нём язык "go". Если вы пришли из Python, Ruby или JavaScript, вы на автомате пишете slice.contains("go") — и получаете красную ошибку компиляции. «Что за ерунда?» — думаете вы. «В любой уважающей себя коллекции есть метод contains!»

Спокойствие, только спокойствие. В Go действительно нет встроенного метода contains для слайсов. И это не баг, а фича. Разработчики языка решили, что одна универсальная функция не покроет все варианты (сравнение строк, чисел, структур), и оставили реализацию за программистом. Звучит страшно, но на деле всё просто и элегантно. В этой статье мы разберём, как написать contains для слайса в Go, рассмотрим производительность, типичные грабли и покажем готовые решения.

Почему в Go нет contains для слайса?

Go — минималистичный язык. В нём нет дженериков (до версии 1.18) и перегрузки методов. Слайс — это не объект с методами, а структура данных: указатель на массив, длина и ёмкость. Разработчики Go решили, что функция contains должна быть явной, чтобы программист всегда понимал, как именно происходит сравнение. Это снижает количество багов и делает код предсказуемым.

Кроме того, contains для слайса — это всегда линейный поиск (O(n)), и если вы ожидаете O(1), то используете не ту структуру (нужна map). Go не хочет маскировать эту сложность.

Самый простой contains для слайса в Go (ручной перебор)

Давайте напишем contains для слайса строк. Это классика, которую вы увидите в любом production-коде:

package main

import "fmt"

// Contains проверяет, есть ли элемент в слайсе строк
func Contains(slice []string, target string) bool {
    for _, item := range slice {
        if item == target {
            return true
        }
    }
    return false
}

func main() {
    languages := []string{"python", "go", "rust", "javascript"}
    fmt.Println(Contains(languages, "go"))       // true
    fmt.Println(Contains(languages, "csharp"))   // false
}

Всё просто: проходим циклом по слайсу, сравниваем каждый элемент с искомым. Если нашли — true, иначе false. Важно: оператор == работает для строк, чисел, булевых значений и указателей. Для структур или слайсов внутри слайса (вложенных) так не прокатит.

Contains для чисел

Для слайса целых чисел код почти идентичен:

func ContainsInt(slice []int, target int) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    fmt.Println(ContainsInt(numbers, 30)) // true
    fmt.Println(ContainsInt(numbers, 99)) // false
}

Неудобно? Да. Но это Go — явность и простота превыше всего.

Универсальный contains для слайса в Go (дженерики, Go 1.18+)

С выходом Go 1.18 в языке появились дженерики (обобщения). Теперь можно написать одну функцию contains для любого типа, поддерживающего сравнение:

package main

import "fmt"

// ContainsGeneric работает для любого comparable типа
func ContainsGeneric[T comparable](slice []T, target T) bool {
    for _, item := range slice {
        if item == target {
            return true
        }
    }
    return false
}

func main() {
    strs := []string{"a", "b", "c"}
    ints := []int{1, 2, 3}
    floats := []float64{1.1, 2.2, 3.3}

    fmt.Println(ContainsGeneric(strs, "b"))   // true
    fmt.Println(ContainsGeneric(ints, 5))     // false
    fmt.Println(ContainsGeneric(floats, 2.2)) // true
}

Ограничение comparable означает, что тип поддерживает операторы == и !=. Это покрывает 90% случаев: строки, числа, указатели, каналы, структуры без слайсов/мап внутри. Для структур с полями-слайсами придётся писать свою логику сравнения.

Альтернативы contains: map и sort.Search

Если вы часто проверяете наличие элементов, не используйте contains для слайса — это O(n) каждый раз. Вот два быстрых варианта:

1. Использовать map (O(1) поиск)

Преобразуйте слайс в map и проверяйте наличие ключа. Идеально для частых проверок:

package main

import "fmt"

func main() {
    langs := []string{"python", "go", "rust"}
    
    // Создаём map для быстрого поиска
    set := make(map[string]struct{}, len(langs))
    for _, v := range langs {
        set[v] = struct{}{}
    }

    // Поиск
    _, exists := set["go"]
    fmt.Println(exists) // true

    _, exists = set["java"]
    fmt.Println(exists) // false
}

struct{}{} — это пустая структура, она не занимает память (0 байт). Такой трюк стандартен для Go-сообщества.

2. Отсортированный слайс + sort.Search (O(log n))

Если слайс отсортирован, можно использовать бинарный поиск из пакета sort:

package main

import (
    "fmt"
    "sort"
)

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    
    // Слайс должен быть отсортирован!
    sort.Ints(numbers)
    
    idx := sort.Search(len(numbers), func(i int) bool {
        return numbers[i] >= 30
    })
    
    if idx < len(numbers) && numbers[idx] == 30 {
        fmt.Println("Найден!")
    } else {
        fmt.Println("Не найден")
    }
}

Этот метод быстрее линейного поиска, но требует отсортированных данных.

Типичные ошибки новичков с contains в Go

  • Сравнение структур с полями-слайсами. == не работает, если структура содержит слайс, мапу или функцию. Используйте reflect.DeepEqual (медленно!) или пишите свой компаратор.
  • Поиск в nil слайсе. В Go можно итерироваться по nil слайсу — цикл просто не выполнится. Но если вы передаёте nil в contains, функция вернёт false, что логично.
  • Забыли про регистр строк. "Go" != "go". Если нужно искать без учёта регистра, используйте strings.EqualFold или приводите к нижнему регистру.
  • Проверка наличия в большом слайсе. Если слайс на 100 000 элементов, линейный поиск — плохая идея. Используйте map.

Contains

Блоги

Рекомендаций по книгам