Чи має конструкція "if x in" схожа на Python?


289

Не можу переглядати весь масив, як я можу перевірити, чи є xв масиві за допомогою Go? Чи є в мові конструкція?

Як і Python: if "x" in array: ...


2
Може бути КОНТРАТИП stackoverflow.com/q/8307478/180100

7
AFAIK, Немає скорочень для цього. Внутрішньо пітон також перебирає масив, і цього немає.
Датський94

6
До речі, важлива примітка до цього полягає в тому, що немає ніякого способу зробити це (як ви просите) " [w] без ітерації по всьому масиву". Зробити такі петлі явними (або за такою функцією, як strings.Index) допомагає зробити більш очевидним, що робить код. У мене in array:складається враження, що, можливо, ти думаєш, що Python робить щось швидке / магічне. AFAIK це не так. Зробити цикл явним допомагає зрозуміти письменника (та всіх читачів) та розглянути інші реалізації (наприклад, карту).
Дейв C

5
Однак якщо "x" у наборі дійсно дуже швидко.
Роберто Альсіна

6
Я не думаю, що ми вимагаємо швидкого програмного підходу, ми просто просимо стислого (і все ще не маємо ...)
Migwell,

Відповіді:


340

Немає вбудованого оператора, який би це зробив у 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.")
}

4
чи можна це зробити, не вказуючи тип? скажімо, якщо я хочу просто загальну голкуInHaystack (голка, стог сіна) без окремих методів для кожного типу
Аллен

25
Це можна зробити з пакетом відображення, але це було б досить неефективно (напевно, приблизно так повільно, як якщо б ви виписали його на такій динамічній мові, як Python). Крім цього, немає. Це те, що люди мають на увазі, коли кажуть, що в Go немає дженериків.
andybalholm

2
Усі, хто натрапив на цю відповідь, повинні зауважити, що НЕ МОЖЕТЕ сортувати карти. Великий недолік використання карт go.
ПідйомСон

4
ви також не можете сортувати карти (об’єкти) у Javascript. Це помилка v8, що об’єкти повертають значення в алфавітному порядку.
кумархарш

7
карти не сортуються на більшості мов - це параметри курсу для структури даних карт (хешмап).
Hejazzman

100

Ще одне рішення, якщо список містить статичні значення.

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

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

11
Так, що якщо ваші "дійсні значення" походять з бази даних?
Рамі Дабайн

Так, це акуратно, але лише тоді, коли ці значення можна визначити заздалегідь.
скарбничка

2
@RonanDejhero тоді я міг би використовувати WHERE: myValue IN (підзапит) :)
anilech

3
Це
чітке

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


10
Це має сенс лише в тому випадку, якщо ви робите повторні пошуки. Інакше ви маєте складність n * log (n) * log (n) для сортування та двійкового пошуку, порівняно з n тільки для лінійного пошуку.
Крістіан

1
Це насправді просто n*log(n) + log(n), оскільки це дві послідовні незалежні операції
pomo_mondreganto

27

Наведений вище приклад використання сортування близький, але у разі рядків просто використовуйте 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

@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

Перемикач перемагає до кінця, найгірший випадок надзвичайно швидший, ніж найкращий випадок. Карти найгірші, і список ближче до переключення.

Тож мораль полягає в тому, що якщо у вас є статичний, досить невеликий список, заява про перемикання - це шлях.


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

Мені цікаво побачити, як
Майкл Дрейпер

А що :замість того , щоб ,в заяві вимикача? Це робить це швидше?
Томас Совейон

Я спробував використати кілька caseзаяв замість одного випадку. Результати певно однакові з обома функціями.
Thomas Sauvajon

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)
        }
    }
}

Ось вона на ігровому майданчику


0

Це настільки близько, як я можу дістатись до природного відчуття оператора "в" Python. Ви повинні визначити власний тип. Тоді ви можете розширити функціональність цього типу, додавши метод типу "має", який веде себе так, як ви сподівалися.

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.