Contém método para uma fatia

214

Existe algo semelhante a um slice.contains(object)método no Go sem precisar fazer uma pesquisa em cada elemento de uma fatia?

vosmith
fonte

Respostas:

226

Mostafa já apontou que esse método é trivial de escrever, e o mkb deu uma dica para você usar a pesquisa binária no pacote de classificação. Mas se você fizer muitas dessas verificações, também poderá usar um mapa.

É trivial verificar se existe uma chave de mapa específica usando o value, ok := yourmap[key]idioma. Como você não está interessado no valor, você também pode criar um, map[string]struct{}por exemplo. Usar um vazio struct{}aqui tem a vantagem de não exigir espaço adicional e o tipo de mapa interno do Go é otimizado para esse tipo de valores. Portanto, map[string] struct{}é uma escolha popular para os sets no mundo Go.

tux21b
fonte
27
Observe também que é necessário escrever struct{}{}para obter o valor da estrutura vazia, para que você possa passá-lo ao seu mapa quando desejar adicionar um elemento. Apenas tente e, se encontrar algum problema, não hesite em perguntar. Você também pode usar a solução da Mostafa se for mais fácil para você entender (a menos que você tenha grandes quantidades de dados).
tux21b
5
A solução é simples, é verdade. Mas o que é preciso para adicionar essa funcionalidade básica ao tempo de execução? Não encontrei esses problemas no repositório Go no github. Isso é triste e estranho.
Igor Petrov
1
Como se map[string] boolcompara com map[string] struct{}. map[string] struct{}parece ser um hack especialmente inicializar um struct vaziostruct {}{}
vadasambar
@IgorPetrov concordou, estou surpreso que um recurso tão básico ainda não esteja no tempo de execução.
jcollum 21/04
180

Não, esse método não existe, mas é trivial escrever:

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

Você pode usar um mapa se essa pesquisa for uma parte importante do seu código, mas os mapas também custam.

Mostafa
fonte
257
Na verdade, isso não é trivial, porque você precisa escrever um para cada tipo que usa e como não há sobrecarga, é necessário nomear cada função de maneira diferente, como em C. append () pode funcionar genericamente porque possui suporte especial ao tempo de execução. Um conteúdo genérico seria útil pelo mesmo motivo, mas realmente a solução genérica é apenas o suporte a genéricos no idioma.
Eloff
15
@Eloffinterface{}
Alex Lockwood
2
@ Alex Lockwood isso realmente funcionará com interfaces?
Ory Banda
101
trivial == 7 linhas de código incluindo 1 loop 1 ramificação if declaração e 1 comparação? Eu acho que estou faltando alguma coisa aqui ...
tothemario
3
Mas por que não adicioná-los no próprio núcleo?
Luna Lovegood
16

Se a fatia é ordenada, há uma busca binária implementado no sortpacote .

mkb
fonte
11

Em vez de usar a slice, mappode ser uma solução melhor.

exemplo simples:

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
fonte
34
Na sua forma atual, esse código não oferece nenhum benefício, pois não faz sentido construir um mapa a partir de uma fatia se você o usar apenas uma vez. - Para ser útil, esse código deve fornecer uma função sliceToMapque faça toda a preparação. Depois disso, consultar o mapa é trivial e eficiente.
Roland Illig 26/10/2015
9

O pacote de classificação fornece os blocos de construção se sua fatia é classificada ou você deseja classificá-la.

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
}

SearchStringpromete retornar the index to insert x if x is not present (it could be len(a)), portanto, uma verificação disso revela se a string está contida na fatia classificada.

Henrik Aasted Sørensen
fonte
Em termos de tempo, a pesquisa regular é O(n)e esta solução o faz O(n*log(n)).
plesiv
@plesiv é uma pesquisa binária, AFAICS. Isso não tornaria O (log n)?
Henrik Aasted Sørensen
sim, a pesquisa binária e a função containssão O(log(n)), mas a abordagem geral se O(n*log(n))deve ao tipo.
plesiv
3

Você pode usar o pacote reflect para iterar sobre uma interface cujo tipo concreto é uma fatia:

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
fonte
3
Claro que você pode usar o pacote de reflexão, mas apenas porque você pode, não significa que você deveria. A reflexão é muito cara.
Justin Ohms
3

Se não for possível usar um mapa para encontrar itens com base em uma chave, considere a ferramenta goderive . O Goderive gera uma implementação específica de tipo de um método contains, tornando seu código legível e eficiente.

Exemplo;

type Foo struct {
    Field1 string
    Field2 int
} 

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

Para gerar o método deriveContainsFoo:

  • Instale o goderive com go get -u github.com/awalterschulze/goderive
  • Executar goderive ./...na pasta da área de trabalho

Este método será gerado para deriveContains:

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

O Goderive tem suporte para alguns outros métodos auxiliares úteis para aplicar um estilo de programação funcional em movimento.

Alexander van Trijffel
fonte
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
fonte
2

Não tenho certeza se os genéricos são necessários aqui. Você só precisa de um contrato para o comportamento desejado. Fazer o seguinte não é mais do que seria necessário em outros idiomas se você desejasse que seus próprios objetos se comportassem em coleções, substituindo Equals () e GetHashCode () por exemplo.

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
fonte
1
"não é mais do que o que você teria que fazer em outros idiomas" não é realmente verdade - por exemplo, o C # Contains()é implementado List<T>, então você só precisa implementar Equals()esse trabalho.
George
1

Criei uma referência muito simples com as soluções dessas respostas.

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

Não é uma referência real, porque, inicialmente, não inseri muitos elementos, mas sinto-me à vontade para alterá-lo.

F. Norbert
fonte
Pensei nisso, mas não é tão representativo devido ao fato de minha máquina não ser tão poderosa.
F. Norbert
0

Pode ser considerado um pouco "hacky", mas dependendo do tamanho e do conteúdo da fatia, você pode unir a fatia e fazer uma pesquisa de string.

Por exemplo, você tem uma fatia contendo valores de palavra única (por exemplo, "sim", "não", "talvez"). Esses resultados são anexados a uma fatia. Se você quiser verificar se esta fatia contém algum resultado "talvez", use

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

A adequação disso realmente depende do tamanho da fatia e do comprimento de seus membros. Pode haver problemas de desempenho ou adequação para fatias grandes ou valores longos, mas para fatias menores de tamanho finito e valores simples, é uma linha única válida para alcançar o resultado desejado.

chopper24
fonte
Não funcionará para situações em que elementos tenham texto semelhante, mas não exatamente o mesmoexSlice := ["yes and no", "maybe", "maybe another"]
Raees Iqbal
Essa é uma abordagem bastante agradável para obter uma solução de uma linha rápida e suja. Você só precisa exigir um delimitador inequívoco (pode ser uma vírgula) e fazer o trabalho extra para ","+strings.Join(exSlice,",")+","",maybe,"
agrupar as
-1

O estilo 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
fonte
2
Isso não responde à pergunta nem fornece informações adicionais.
Croolman