Zawiera metodę dla plasterka

214

Czy istnieje coś podobnego do slice.contains(object)metody w Go bez konieczności przeszukiwania każdego elementu w plasterku?

vosmith
źródło

Odpowiedzi:

226

Mostafa wskazał już, że taka metoda jest trywialna do napisania, a mkb dał ci wskazówkę, jak korzystać z wyszukiwania binarnego z pakietu sortowania. Ale jeśli zamierzasz wykonać wiele takich kontroli, możesz również rozważyć użycie mapy.

Sprawdzanie, czy istnieje określony klucz mapy, jest proste za pomocą value, ok := yourmap[key]idiomu. Ponieważ nie jesteś zainteresowany tą wartością, możesz również utworzyć map[string]struct{}na przykład. Korzystanie z pustego struct{}miejsca ma tę zaletę, że nie wymaga dodatkowej przestrzeni, a wewnętrzny typ mapy Go jest zoptymalizowany dla tego rodzaju wartości. Dlatego map[string] struct{}jest popularnym wyborem dla zestawów w świecie Go.

tux21b
źródło
27
Pamiętaj również, że musisz napisać, struct{}{}aby uzyskać wartość pustej struktury, abyś mógł przekazać ją do mapy, gdy chcesz dodać element. Po prostu spróbuj, a jeśli napotkasz jakiekolwiek problemy, możesz zapytać. Możesz także skorzystać z rozwiązania Mostafa, jeśli łatwiej to zrozumieć (chyba że masz ogromne ilości danych).
tux21b
5
Rozwiązanie jest proste, to prawda. Ale co trzeba, aby dodać taką podstawową funkcjonalność do środowiska wykonawczego? Nie znalazłem takich problemów w repozytorium Go na github. To smutne i dziwne.
Igor Pietrow
1
Jak wypada map[string] boolporównanie map[string] struct{}. map[string] struct{}wygląda na włamanie szczególnie inicjujące pustą strukturęstruct {}{}
vadasambar
@IgorPetrov zgodził się, jestem zaskoczony, że tak podstawowej funkcji nie ma już w środowisku uruchomieniowym.
jcollum
180

Nie, taka metoda nie istnieje, ale napisanie jej jest trywialne:

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

Możesz użyć mapy, jeśli to wyszukiwanie jest ważną częścią twojego kodu, ale mapy też mają swoje koszty.

Mostafa
źródło
257
W rzeczywistości nie jest to trywialne, ponieważ musisz napisać po jednym dla każdego używanego typu, a ponieważ nie ma przeciążenia, musisz nazwać każdą funkcję inaczej, tak jak w C. append () może działać ogólnie, ponieważ ma specjalne wsparcie środowiska wykonawczego. Ogólny zawiera byłby użyteczny z tego samego powodu, ale tak naprawdę ogólnym rozwiązaniem jest po prostu ogólne wsparcie w języku.
Eloff,
15
@Eloffinterface{}
Alex Lockwood
2
@Alex Lockwood czy to faktycznie będzie działać z interfejsami?
Ory Band
101
trywialny == 7 linii kodu, w tym 1 pętla 1 gałąź if instrukcja i 1 porównanie? Myślę, że coś mi tu brakuje ...
tothemario,
3
Ale dlaczego nie dodać tych samych elementów do samego rdzenia?
Luna Lovegood
11

Zamiast używać slice, mapmoże być lepszym rozwiązaniem.

prosty przykład:

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
źródło
34
W obecnej formie ten kod nie przynosi żadnych korzyści, ponieważ nie ma sensu budować mapy z wycinka, jeśli zamierzasz go użyć tylko raz. - Aby był przydatny, ten kod powinien raczej zapewniać funkcję, sliceToMapktóra wykonuje wszystkie przygotowania. Następnie wyszukiwanie mapy jest banalne i wydajne.
Roland Illig,
9

Rodzaj pakiet zawiera budulcem jeśli plaster jest posortowana czy jesteś gotów go uporządkować.

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
}

SearchStringzapowiada powrót the index to insert x if x is not present (it could be len(a)), więc sprawdzenie tego ujawnia, czy łańcuch zawiera posortowany plasterek.

Henrik Aasted Sørensen
źródło
Jeśli chodzi o czas, regularne wyszukiwanie jest możliwe O(n)i dzięki temu rozwiązaniu O(n*log(n)).
plesiv
@plesiv to wyszukiwanie binarne, AFAICS. Czy to nie oznaczałoby, że O (log n)?
Henrik Aasted Sørensen
tak, wyszukiwanie binarne i funkcja containsO(log(n)), ale ogólne podejście O(n*log(n))wynika z tego rodzaju.
plesiv
3

Możesz użyć pakietu odbicia do iteracji po interfejsie, którego konkretnym typem jest plasterek:

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

Ethan Kennedy
źródło
3
Jasne, że możesz użyć pakietu odbicia, ale tylko dlatego, że możesz, nie oznacza to, że powinieneś. Odbicie jest bardzo drogie.
Justin Ohms
3

Jeśli nie można użyć mapy do znalezienia przedmiotów opartych na kluczu, możesz rozważyć narzędzie goderive . Goderive generuje specyficzną dla typu implementację metody zawiera, dzięki czemu kod jest czytelny i wydajny.

Przykład;

type Foo struct {
    Field1 string
    Field2 int
} 

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

Aby wygenerować metodę deriveContainsFoo:

  • Zainstaluj goderive za pomocą go get -u github.com/awalterschulze/goderive
  • Uruchom goderive ./...w folderze obszaru roboczego

Ta metoda zostanie wygenerowana dla DeriveContains:

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

Goderive obsługuje wiele innych przydatnych metod pomocniczych, pozwalających na zastosowanie funkcjonalnego stylu programowania.

Alexander van Trijffel
źródło
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
}
Jim Hsiang
źródło
2

Nie jestem pewien, czy potrzebne są tutaj leki generyczne. Potrzebujesz tylko umowy na swoje pożądane zachowanie. Wykonanie następujących czynności jest niczym więcej niż tym, co musiałbyś zrobić w innych językach, jeśli chcesz, aby własne obiekty zachowywały się same w kolekcjach, na przykład poprzez zastąpienie Equals () i 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
źródło
1
„nie jest niczym więcej niż to, co musiałbyś robić w innych językach” nie jest tak naprawdę prawdą - np. w C # Contains()jest zaimplementowany List<T>, więc musisz tylko zaimplementować Equals()tę pracę.
George
1

Stworzyłem bardzo prosty test porównawczy z rozwiązaniami z tych odpowiedzi.

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

To nie jest prawdziwy punkt odniesienia, ponieważ początkowo nie wstawiłem zbyt wielu elementów, ale mogę je rozwidlać i zmieniać.

F. Norbert
źródło
Myślałem o tym, ale nie jest tak reprezentatywny, ponieważ moja maszyna nie jest tak potężna.
F. Norbert
0

Może to być uważane za nieco „hacky”, ale w zależności od wielkości i zawartości wycinka możesz połączyć wycinek i przeprowadzić wyszukiwanie łańcuchowe.

Na przykład masz plasterek zawierający wartości pojedynczych słów (np. „Tak”, „nie”, „może”). Te wyniki są dołączane do wycinka. Jeśli chcesz sprawdzić, czy ten plasterek zawiera jakieś wyniki „być może”, możesz użyć

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

To, jak odpowiednie jest to naprawdę, zależy od wielkości plastra i długości jego elementów. Mogą występować problemy z wydajnością lub przydatnością w przypadku dużych wycinków lub długich wartości, ale w przypadku mniejszych wycinków o skończonych rozmiarach i prostych wartościach można uzyskać pożądany wynik tylko w jednym wierszu.

chopper24
źródło
Nie będzie działać w sytuacji, gdy elementy mają podobny tekst, ale nie dokładnie taki samexSlice := ["yes and no", "maybe", "maybe another"]
Raees Iqbal
Jest to dość przyjemne podejście do uzyskania szybkiego i brudnego rozwiązania z jedną linią. Musisz tylko wymagać jednoznacznego separatora (może być przecinkiem) i wykonać dodatkową pracę, aby ","+strings.Join(exSlice,",")+","",maybe,"
spakować
-1

Styl 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"
})
Roy O'Young
źródło
2
To nie odpowiada na pytanie ani nie zawiera dodatkowych informacji.
Croolman