Содержит метод для среза

215

Есть ли что-то похожее на slice.contains(object)метод в Go без необходимости выполнять поиск по каждому элементу в срезе?

vosmith
источник

Ответы:

227

Мостафа уже указал, что такой метод написать тривиально, и mkb подсказал вам использовать двоичный поиск из пакета sort. Но если вы собираетесь делать много таких проверок, вы можете использовать карту.

С помощью этой value, ok := yourmap[key]идиомы легко проверить, существует ли определенный ключ карты . Поскольку вас не интересует значение, вы можете также создать, map[string]struct{}например. Использование struct{}здесь пустого имеет то преимущество, что оно не требует дополнительного пространства, а внутренний тип карты Go оптимизирован для таких значений. Таким образом, map[string] struct{}это популярный выбор для наборов в мире Go.

tux21b
источник
27
Также обратите внимание, что вы должны написать, struct{}{}чтобы получить значение пустой структуры, чтобы вы могли передать ее на карту, когда вы хотите добавить элемент. Просто попробуйте, и если у вас возникнут какие-либо проблемы, не стесняйтесь спрашивать. Вы также можете использовать решение Mostafa, если вам легче его понять (если у вас нет огромных объемов данных).
tux21b
5
Решение простое, это правда. Но что нужно, чтобы добавить такую ​​базовую функциональность во время выполнения? Я не нашел таких проблем в репо Go на github. Это грустно и странно.
Игорь Петров
1
Как map[string] boolсравнить с map[string] struct{}. map[string] struct{}кажется хаком, особенно инициализация пустой структурыstruct {}{}
vadasambar
@IgorPetrov согласился, я удивлен, что такая базовая функция еще не во время выполнения.
Jcollum
180

Нет, такого метода не существует, но написать тривиально:

func contains(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

Вы можете использовать карту, если этот поиск является важной частью вашего кода, но карты тоже стоят.

Мостафа
источник
258
На самом деле это не тривиально, потому что вы должны написать по одному для каждого типа, который вы используете, и поскольку нет перегрузки, вы должны называть каждую функцию по-разному, как в C. Универсальное содержимое будет полезно по той же причине, но на самом деле универсальное решение - это просто поддержка обобщений в языке.
Eloff
15
@Eloffinterface{}
Алекс Локвуд
2
@ Алекс Локвуд будет ли это работать с интерфейсами?
Ory Band
101
тривиально == 7 строк кода, включая 1 цикл, 1 ветвь оператора if и 1 сравнение? Я думаю, что я что-то здесь
упускаю
3
Но почему бы не добавить их в само ядро?
Луна Лавгуд
13

Вместо того чтобы использовать slice, mapможет быть лучшим решением.

простой пример:

package main

import "fmt"


func contains(slice []string, item string) bool {
    set := make(map[string]struct{}, len(slice))
    for _, s := range slice {
        set[s] = struct{}{}
    }

    _, ok := set[item] 
    return ok
}

func main() {

    s := []string{"a", "b"}
    s1 := "a"
    fmt.Println(contains(s, s1))

}

http://play.golang.org/p/CEG6cu4JTf

holys
источник
34
В своем нынешнем виде этот код не дает никаких преимуществ, поскольку нет смысла создавать карту из среза, если вы собираетесь использовать ее только один раз. - Чтобы быть полезным, этот код должен скорее обеспечивать функцию, sliceToMapкоторая делает всю подготовку. После этого запросы к карте тривиальны и эффективны.
Роланд Иллиг
9

Пакет сортировки предоставляет строительные блоки, если ваш фрагмент отсортирован или вы хотите его отсортировать.

input := []string{"bird", "apple", "ocean", "fork", "anchor"}
sort.Strings(input)

fmt.Println(contains(input, "apple")) // true
fmt.Println(contains(input, "grow"))  // false

...

func contains(s []string, searchterm string) bool {
    i := sort.SearchStrings(s, searchterm)
    return i < len(s) && s[i] == searchterm
}

SearchStringобещает вернуть the index to insert x if x is not present (it could be len(a)), так что проверка показывает, содержит ли строка отсортированный фрагмент.

Хенрик Аастед Серенсен
источник
С точки зрения времени, регулярный поиск есть, O(n)и это решение делает его O(n*log(n)).
Плесив
@plesiv это бинарный поиск, AFAICS. Разве это не сделало бы это O (log n)?
Хенрик Аастед Серенсен
да, бинарный поиск и функция containsесть O(log(n)), но общий подход O(n*log(n))обусловлен сортировкой.
Плесив
3

Вы можете использовать отражающий пакет для перебора интерфейса, конкретным типом которого является срез:

func HasElem(s interface{}, elem interface{}) bool {
    arrV := reflect.ValueOf(s)

    if arrV.Kind() == reflect.Slice {
        for i := 0; i < arrV.Len(); i++ {

            // XXX - panics if slice element points to an unexported struct field
            // see https://golang.org/pkg/reflect/#Value.Interface
            if arrV.Index(i).Interface() == elem {
                return true
            }
        }
    }

    return false
}

https://play.golang.org/p/jL5UD7yCNq

Этан Кеннеди
источник
3
Конечно, вы можете использовать пакет отражения, но только потому, что вы можете, не значит, что вы должны. Отражение очень дорогое.
Джастин
3

Если использовать карту для поиска элементов по ключу нецелесообразно , вы можете воспользоваться инструментом goderive . Goderive генерирует специфическую для типа реализацию метода contains, делая ваш код читабельным и эффективным.

Пример;

type Foo struct {
    Field1 string
    Field2 int
} 

func Test(m Foo) bool {
     var allItems []Foo
     return deriveContainsFoo(allItems, m)
}

Чтобы сгенерировать метод diverveContainsFoo:

  • Установите Goderive с go get -u github.com/awalterschulze/goderive
  • Запустите goderive ./...в вашей папке рабочего пространства

Этот метод будет сгенерирован для DeriveContains:

func deriveContainsFoo(list []Foo, item Foo) bool {
    for _, v := range list {
        if v == item {
            return true
        }
    }
    return false
}

Goderive поддерживает довольно много других полезных вспомогательных методов для применения функционального стиля программирования в go.

Александр ван Трижеффель
источник
2
func Contain(target interface{}, list interface{}) (bool, int) {
    if reflect.TypeOf(list).Kind() == reflect.Slice || reflect.TypeOf(list).Kind() == reflect.Array {
        listvalue := reflect.ValueOf(list)
        for i := 0; i < listvalue.Len(); i++ {
            if target == listvalue.Index(i).Interface() {
                return true, i
            }
        }
    }
    if reflect.TypeOf(target).Kind() == reflect.String && reflect.TypeOf(list).Kind() == reflect.String {
        return strings.Contains(list.(string), target.(string)), strings.Index(list.(string), target.(string))
    }
    return false, -1
}
Джим Сян
источник
2

Не уверен, что здесь нужны дженерики. Вам просто нужен контракт для вашего желаемого поведения. Выполнение следующего не более чем то, что вы должны были бы сделать на других языках, если бы вы хотели, чтобы ваши собственные объекты вели себя в коллекциях, переопределяя, например, Equals () и GetHashCode ().

type Identifiable interface{
    GetIdentity() string
}

func IsIdentical(this Identifiable, that Identifiable) bool{
    return (&this == &that) || (this.GetIdentity() == that.GetIdentity())
}

func contains(s []Identifiable, e Identifiable) bool {
    for _, a := range s {
        if IsIdentical(a,e) {
            return true
        }
    }
    return false
}
JonPen
источник
1
«не больше, чем то, что вы должны были бы делать на других языках», на самом деле не так - например, в C # Contains()реализован List<T>, так что вам нужно всего лишь реализовать его Equals()для этой работы.
Джордж,
1

Я создал очень простой тест с решениями из этих ответов.

https://gist.github.com/NorbertFenk/7bed6760198800207e84f141c41d93c7

Это не настоящий эталон, потому что изначально я не вставил слишком много элементов, но не стесняюсь раскошелиться и изменить его.

Ф. Норберт
источник
Я думал об этом, но он не такой представительный, потому что моя машина не такая мощная.
Ф. Норберт
-1

Стиль Go:

func Contains(n int, match func(i int) bool) bool {
    for i := 0; i < n; i++ {
        if match(i) {
            return true
        }
    }
    return false
}


s := []string{"a", "b", "c", "o"}
// test if s contains "o"
ok := Contains(len(s), func(i int) bool {
    return s[i] == "o"
})
Рой О'Юнг
источник
2
Это не отвечает на вопрос и не дает дополнительную информацию.
Croolman
-1

Это может считаться немного «хакерским», но в зависимости от размера и содержимого фрагмента вы можете объединить фрагмент и выполнить поиск по строке.

Например, у вас есть фрагмент, содержащий значения одного слова (например, «да», «нет», «возможно»). Эти результаты добавляются к срезу. Если вы хотите проверить, содержит ли этот срез какие-либо «возможно» результаты, вы можете использовать

exSlice := ["yes", "no", "yes", "maybe"]
if strings.Contains(strings.Join(exSlice, ","), "maybe") {
  fmt.Println("We have a maybe!")
}

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

chopper24
источник
Не будет работать для ситуации, когда элементы имеют похожий текст, но не совсем одинаковыйexSlice := ["yes and no", "maybe", "maybe another"]
Raees Iqbal
Это довольно хороший подход для достижения быстрого и грязного однострочного решения. Вам просто нужно указать однозначный разделитель (может быть запятой) и выполнить дополнительную работу, чтобы заключить в скобки обе строки:, ","+strings.Join(exSlice,",")+","и",maybe,"
nobar