Какие существуют способы проверить наличие элемента в слайсе в Go? Опишите их преимущества, недостатки и сложность.

Ответ

В Go нет встроенной функции для проверки наличия элемента в слайсе, которая была бы доступна во всех версиях языка, но существует несколько стандартных подходов. Выбор зависит от версии Go, размера слайса и частоты операций поиска.

1. Простой перебор (цикл for)

Это самый базовый и универсальный способ, который работает во всех версиях Go.

Преимущества:

  • Простота и наглядность.
  • Не требует дополнительных зависимостей или версий Go.

Недостатки:

  • Низкая производительность на больших слайсах.

Сложность: O(n), где n — количество элементов в слайсе.

func containsString(slice []string, item string) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

2. Generic-функция (Go 1.18+)

С появлением дженериков в Go 1.18 можно написать универсальную функцию для любых сравниваемых типов (comparable).

Преимущества:

  • Переиспользуемый код для разных типов (int, string, float64 и т.д.).
  • Сохраняет типобезопасность.

Сложность: O(n).

func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

3. Пакет slices (Go 1.21+)

Начиная с версии Go 1.21, в стандартной библиотеке появился пакет slices, который содержит функцию Contains. Это предпочтительный способ для современных проектов.

Преимущества:

  • Идиоматичный и стандартный способ.
  • Код становится чище и короче.

Сложность: O(n).

import "slices"

func check() {
    mySlice := []string{"a", "b", "c"}

    // Вернет true
    found := slices.Contains(mySlice, "b") 
}

4. Использование map для частых проверок

Если проверки на наличие элемента выполняются многократно на одном и том же наборе данных, эффективнее будет преобразовать слайс в map.

Преимущества:

  • Очень высокая скорость поиска после первоначального построения map.

Недостатки:

  • Требует дополнительной памяти для хранения map.
  • Затраты на первоначальное преобразование слайса в map (сложность O(n)).

Сложность:

  • Построение map: O(n)
  • Поиск в map: O(1) (в среднем)

Этот подход оправдан, когда количество операций поиска значительно превышает размер слайса.

// Преобразуем слайс в set (map[T]struct{}) для быстрого поиска
stringSlice := []string{"apple", "banana", "orange"}
set := make(map[string]struct{}, len(stringSlice))
for _, v := range stringSlice {
    set[v] = struct{}{}
}

// Проверка выполняется мгновенно
if _, ok := set["banana"]; ok {
    // Элемент найден
}

Использование struct{} в качестве значения map является идиомой в Go для создания множества (set), так как пустая структура не занимает памяти.

Ответ 18+ 🔞

Слушай, а вот это реально пиздец, как в Го нет нормальной встроенной хуйни для проверки, есть ли элемент в слайсе. Ну, то есть, она есть, но только с определённых версий, а так — приходится выёбываться. Но варианты есть, выбирай по обстановке, блядь.

1. Тупой перебор (цикл for)

Это как ломом лед рубить — просто, понятно и работает везде, даже на древнем Го.

Что хорошего:

  • Не надо думать, написал и пошёл.
  • Никаких новых версий не требует.

Что плохого:

  • Если слайс размером с овердохуища, то всё, пиздец, будет тормозить как старая кобыла.

Скорость: O(n), где n — это сколько там у тебя элементов накопилось.

func containsString(slice []string, item string) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

2. Дженерики (Go 1.18+)

Тут уже поинтереснее, с версии 1.18 можно написать одну функцию на все случаи жизни, для любых типов, которые можно сравнивать.

Что хорошего:

  • Одна функция и для строк, и для чисел, и для всего, что comparable. Удобно, ёпта.
  • Типы не теряются, всё типобезопасно.

Скорость: Всё те же O(n), волшебства нет.

func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

3. Пакет slices (Go 1.21+)

А вот это уже красота, блядь! С версии 1.21 в стандартной библиотеке завезли пакет slices, и там есть готовая Contains. Вот это сейчас самый правильный путь, если пишешь на свежем Го.

Что хорошего:

  • Красиво, идиоматично, одна строчка.
  • Не надо свою велосипедную хуйню городить.

Скорость: Да всё та же O(n), но зато стильно.

import "slices"

func check() {
    mySlice := []string{"a", "b", "c"}

    // Вернёт true, ёбана
    found := slices.Contains(mySlice, "b") 
}

4. Использование map для частых проверок

А вот это, сука, для настоящих перфекционистов! Если тебе надо один слайс тыкать раз сто проверкой, то лучше его один раз преобразовать в map и потом наслаждаться жизнью.

Что хорошего:

  • Скорость поиска — просто пиздец какая, почти мгновенно.

Что плохого:

  • Жрёт память, как не в себя, под map надо место.
  • Сначала надо этот map построить, а это тоже время.

Скорость:

  • Построить map: O(n) (один раз потерпеть)
  • Найти в map: O(1) (а вот это уже охуенно!)

Смысл есть, только если ищешь часто, а данные не меняются каждую секунду.

// Делаем из слайса сет (map[T]struct{}) для быстрого поиска
stringSlice := []string{"apple", "banana", "orange"}
set := make(map[string]struct{}, len(stringSlice))
for _, v := range stringSlice {
    set[v] = struct{}{}
}

// Проверка — раз плюнуть
if _, ok := set["banana"]; ok {
    // Ну вот, нашёл, доволен как слон
}

Кстати, struct{} в качестве значения — это такая местная приколюха, чтоб память не жрало. Гениально и просто, в рот меня чих-пых!