Имеет ли Go конструкцию «if x in», похожую на Python?


291

Без итерации по всему массиву, как я могу проверить, если xв массиве с помощью Go? У языка есть конструкция?

Как и Python: if "x" in array: ...


2
Может быть,

7
AFAIK, в го нет сокращения для этого. Внутренне, python также выполняет итерации по массиву, это не обходится.
Danish94

6
Кстати, важное примечание к этому заключается в том, что нет способа сделать это (как вы просите) « [без] итерации по всему массиву». Явное создание таких циклов (или таких функций, как strings.Index) помогает сделать более понятным, что делает код. У меня создается впечатление, что, возможно, вы думаете, что Python in array:делает что-то быстрое / волшебное. AFAIK это не так. Явное создание цикла помогает автору (и всем читателям) осознать и рассмотреть другие реализации (например, карту).
Дейв С

5
Однако, если «х» в наборе действительно очень быстро.
Роберто Альсина

6
Я не думаю, что мы просим о программно быстром подходе, мы просто просим о
кратком

Ответы:


341

В Go нет встроенного оператора для этого. Вам нужно перебрать массив. Вы можете написать свою собственную функцию, например:

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

Если вы хотите иметь возможность проверять членство без перебора всего списка, вам нужно использовать карту вместо массива или среза, например так:

visitedURL := map[string]bool {
    "http://www.google.com": true,
    "https://paypal.com": true,
}
if visitedURL[thisSite] {
    fmt.Println("Already been here.")
}

5
Есть ли способ сделать это без указания типа? скажем, если я хочу просто общую функцию needleInHaystack (needle, haystack) без отдельных методов для каждого типа
Аллен

27
Это можно сделать с помощью пакетаlect, но это будет довольно неэффективно (вероятно, примерно так же медленно, как если бы вы написали это на динамическом языке, таком как Python). Кроме этого нет. Вот что имеют в виду люди, когда говорят, что у Go нет дженериков.
andybalholm

2
Любой, кто наткнулся на этот ответ, должен заметить, что вы НЕ МОЖЕТЕ сортировать карты. Большой недостаток в использовании карт.
RisingSun

4
Вы также не можете сортировать карты (объекты) в Javascript. Это ошибка v8, из-за которой объекты возвращают значения в алфавитном порядке.
kumarharsh

7
Карты не сортируются на большинстве языков - это нормально для структуры данных карты (hashmap).
Hejazzman

101

Другое решение, если список содержит статические значения.

Например: проверка допустимого значения из списка допустимых значений:

func IsValidCategory(category string) bool {
    switch category {
    case
        "auto",
        "news",
        "sport",
        "music":
        return true
    }
    return false
}

13
Да, что, если ваши «действительные значения» приходят из базы данных?
Рами Дабейн

Да, это аккуратно, но только когда эти значения могут быть определены заранее.
piggybox

3
@RonanDejhero, тогда я мог бы использовать ГДЕ: myValue IN (подзапрос) :)
anilech

3
Это аккуратно по сравнению с главным ответом
piggybox

50

Это цитата из книги «Программирование на ходу: создание приложений для 21-го века»:

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

Go предоставляет метод sort.Search (), который использует алгоритм двоичного поиска: для этого требуется сравнение только log2 (n) элементов (где n - количество элементов) каждый раз. Чтобы представить это в перспективе, линейный поиск 1000000 элементов требует в среднем 500000 сравнений, а наихудший случай - 1000000 сравнений; бинарный поиск требует не более 20 сравнений, даже в худшем случае.

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.Search(len(files),
    func(i int) bool { return files[i] >= target })
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

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


11
Это имеет смысл, только если вы делаете повторный поиск. В противном случае у вас есть сложность n * log (n) * log (n) для сортировки и бинарного поиска, а не просто n для линейного поиска.
Кристиан

1
Это на самом деле просто n*log(n) + log(n), так как это две последовательные независимые операции
pomo_mondreganto

27

Приведенный выше пример использования sort близок, но в случае строк просто используйте SearchString:

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.SearchStrings(files, target)
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

https://golang.org/pkg/sort/#SearchStrings


2
Этот ответ выглядит как менее информативная копия вставки ответа ниже, которая произошла до этого ответа.
Цитин

@cytinus Какой ответ вы имеете в виду? Это единственный, который я вижу на основе sort.SearchStrings.
аким

1
Я получил 100-кратное ускорение при повторных поисках большого среза.
Xeoncross

20

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

Я сравнил лучшие и худшие сценарии 3 типов поиска:

  • используя карту
  • используя список
  • используя оператор switch

вот код функции:

func belongsToMap(lookup string) bool {
list := map[string]bool{
    "900898296857": true,
    "900898302052": true,
    "900898296492": true,
    "900898296850": true,
    "900898296703": true,
    "900898296633": true,
    "900898296613": true,
    "900898296615": true,
    "900898296620": true,
    "900898296636": true,
}
if _, ok := list[lookup]; ok {
    return true
} else {
    return false
}
}


func belongsToList(lookup string) bool {
list := []string{
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636",
}
for _, val := range list {
    if val == lookup {
        return true
    }
}
return false
}

func belongsToSwitch(lookup string) bool {
switch lookup {
case
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636":
    return true
}
return false
}

в лучших случаях выбирают первый элемент в списках, в худших - несуществующее значение.

Вот результаты:

BenchmarkBelongsToMapWorstCase-4 2000000 787 ns/op BenchmarkBelongsToSwitchWorstCase-4 2000000000 0.35 ns/op BenchmarkBelongsToListWorstCase-4 100000000 14.7 ns/op BenchmarkBelongsToMapBestCase-4 2000000 683 ns/op BenchmarkBelongsToSwitchBestCase-4 100000000 10.6 ns/op BenchmarkBelongsToListBestCase-4 100000000 10.4 ns/op

Переключатель выигрывает до конца, в худшем случае это намного быстрее, чем в лучшем случае. Карты являются худшими, и список ближе к переключению.

Итак, мораль такова: если у вас есть статичный, достаточно небольшой список, оператор switch - это путь.


Я не знаю, оптимизирует ли Go этот случай, но имеет ли это значение, если вы переместите инициализацию списка / карты за пределы тестовой функции?
w00t

Мне любопытно посмотреть, как сравнение получится с отсортированным списком и будет ли сортировка того стоить
Майкл Дрейпер

А что :вместо вместо ,в выражении switch? Это делает это быстрее?
Томас Соваджон

Я попытался использовать несколько caseутверждений вместо одного случая. Результаты практически одинаковы для обеих функций.
Томас Соваджон

7

Другой вариант - использовать карту как набор. Вы используете только ключи, и значение имеет что-то вроде логического значения, которое всегда верно. Тогда вы можете легко проверить, содержит ли карта ключ или нет. Это полезно, если вам нужно поведение набора, где, если вы добавляете значение несколько раз, оно входит в набор только один раз.

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

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    var MAX int = 10

    m := make(map[int]bool)

    for i := 0; i <= MAX; i++ {
        m[rand.Intn(MAX)] = true
    }

    for i := 0; i <= MAX; i++ {
        if _, ok := m[i]; ok {
            fmt.Printf("%v is in map\n", i)
        } else {
            fmt.Printf("%v is not in map\n", i)
        }
    }
}

Вот она на ходу детская площадка


3

Это как можно ближе к естественному ощущению оператора «in» в Python. Вы должны определить свой собственный тип. Затем вы можете расширить функциональность этого типа, добавив метод типа has, который ведет себя так, как вы надеетесь.

package main

import "fmt"

type StrSlice []string

func (list StrSlice) Has(a string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func main() {
    var testList = StrSlice{"The", "big", "dog", "has", "fleas"}

    if testList.Has("dog") {
        fmt.Println("Yay!")
    }
}

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

Да, он работает за линейное время, но это не главное. Суть в том, чтобы спросить и узнать, какие общие языковые конструкции есть и нет у Go. Это хорошее упражнение. Является ли этот ответ глупым или полезным, зависит от читателя.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.