ChatGPT解决这个技术问题 Extra ChatGPT

包含切片的方法

有没有类似于 Go 中的 slice.contains(object) 方法而无需搜索切片中的每个元素的方法?

尽量不要使用第三方包来完成这样的工作,比如你提供的@Rodrigo。它使您的代码变得庞大而脆弱

t
tshepang

Mostafa 已经pointed out认为这样的方法编写起来很简单,并且 mkb 给了您使用 sort 包中的二进制搜索的提示。但是,如果您要进行大量此类包含检查,您也可以考虑使用地图。

使用 value, ok := yourmap[key] 习语检查是否存在特定的映射键很简单。由于您对该值不感兴趣,因此您还可以创建一个 map[string]struct{} 例如。在这里使用空的 struct{} 的好处是它不需要任何额外的空间,并且 Go 的内部映射类型针对这种值进行了优化。因此,map[string] struct{} 是围棋世界中集合的流行选择。


另请注意,您必须编写 struct{}{} 来获取空结构的值,以便在您想要添加元素时将其传递给您的地图。试试看,如果遇到任何问题,请随时提问。如果您更容易理解,您也可以使用 Mostafa 的解决方案(除非您有大量数据)。
解决方法很简单,没错。但是将这些基本功能添加到运行时需要什么?我在 github 上的 Go repo 中没有发现这样的问题。这是可悲和奇怪的。
map[string] boolmap[string] struct{} 相比如何。 map[string] struct{} 似乎是一种 hack,尤其是初始化一个空结构 struct {}{}
@IgorPetrov 同意,我很惊讶这样一个基本功能还没有在运行时中。
荒谬的是你必须自己添加这个。
M
Mik

不,这种方法不存在,但写起来很简单:

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

如果查找是代码的重要部分,您可以使用地图,但地图也有成本。


实际上这不是微不足道的,因为您必须为您使用的每种类型编写一个,并且因为没有重载,您必须以不同的方式命名每个函数,就像在 C 中一样。 append() 可以通用,因为它具有特殊的运行时支持。出于同样的原因,泛型包含会很有用,但实际上泛型解决方案只是语言中的泛型支持。
琐碎 == 7 行代码,包括 1 个循环 1 个分支 if 语句和 1 个比较?我想我在这里遗漏了一些东西......
@tothemario 这个答案表明它对 write 微不足道......您的回答(正确地)表明它对 run 不一定微不足道。
但是为什么不将这些添加到 go core 本身呢?
如果在这方面像 C 一样痛苦,那么 Go 的意义何在...如果 contains 如此微不足道,将其添加到标准库中应该是不言自明的。
A
Adolfo

从 Go 1.18 开始,您可以使用 slices 包 - 特别是通用 Contains 函数:https://pkg.go.dev/golang.org/x/exp/slices#Contains

go get golang.org/x/exp/slices
import  "golang.org/x/exp/slices"
things := []string{"foo", "bar", "baz"}
slices.Contains(things, "foo") // true

请注意,由于它作为实验包在 stdlib 之外,因此它不受 Go 1 Compatibility Promise™ 的约束,并且在正式添加到 stdlib 之前可能会发生变化。


我敢打赌它将成为 Go v2.0.0 标准库的一部分。
不幸的是,您不能将 Go 1.18 与旧版本的 macOS 一起使用
H
Henrik Aasted Sørensen

如果您的切片已排序或您愿意对其进行排序,sort 包将提供构建块。

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)吗?
是的,二分搜索和函数 containsO(log(n)),但由于排序,整体方法是 O(n*log(n))
@plesiv 是的,单次搜索确实如此,但如果搜索多次,比如 n 次,则为 O(n + n * log(n)) 与 O(n * n)。 Henrik 的回答显示搜索不止一次。
A
Amar

在 Go 1.18+ 中,我们可以使用泛型。

func Contains[T comparable](s []T, e T) bool {
    for _, v := range s {
        if v == e {
            return true
        }
    }
    return false
}

Go 是我最喜欢的语言,因为我喜欢从头开始创建其他语言提供的 OOTB 实用程序。
@AbhijitSarkar我意识到你在开玩笑,我也同意这应该在stdlib中,但是泛型刚刚被引入Go。我更喜欢一种对其引入的功能非常慎重并且保持相对简单的语言。我希望随着时间的推移,这将被添加到 Golang 中。
h
holys

除了使用 slicemap 可能是更好的解决方案。

简单的例子:

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


在目前的形式中,这段代码没有任何好处,因为如果你只打算使用一次切片,那么从切片构造一个地图是没有意义的。 — 为了有用,这段代码应该提供一个函数 sliceToMap 来完成所有准备工作。之后,查询地图变得简单而高效。
您正在通过迭代切片来创建地图,这是双倍的工作,更多的代码,更低的效率
M
Matt K

如果切片已排序,则在 the sort package 中实现了二进制搜索。


S
Stephen Rauch
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
}

T
Tim S. Van Haren

您可以使用 reflect 包来迭代具体类型为切片的接口:

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


当然你可以使用反射包,但仅仅因为你可以,并不意味着你应该。反射非常昂贵。
在实际的应用程序代码中,您不应该这样做。它是昂贵的。但是,对于单元测试来说,它并不重要,而且非常有用。
A
Alexander van Trijffel

如果使用地图根据键查找项目不可行,您可以考虑使用 goderive 工具。 Goderive 生成特定于类型的 contains 方法实现,使您的代码既可读又高效。

例子;

type Foo struct {
    Field1 string
    Field2 int
} 

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

要生成 derivedContainsFoo 方法:

使用 go get -u github.com/awalterschulze/goderive 安装 goderive

在您的工作区文件夹中运行 goderive ./...

将为deriveContains 生成此方法:

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

Goderive 支持一些其他有用的帮助方法来在 go 中应用函数式编程风格。


T
Taavi

不确定这里是否需要泛型。你只需要一个你想要的行为的合同。如果您希望自己的对象在集合中表现自己,例如通过覆盖 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
}

“只不过是您在其他语言中必须做的事情”并不是真的 - 例如在 C# 中,Contains() 是在 List<T> 上实现的,因此您只需要实现 Equals() 即可完成这项工作。
s
sfmirtalebi

我认为 map[x]boolmap[x]struct{} 更有用。

为不存在的项目索引地图将返回 false。因此,您可以只说 m[X] 而不是 _, ok := m[X]

这使得在表达式中嵌套包含测试变得容易。


R
Roy O'Young

围棋风格:

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

这没有回答问题,也没有提供额外的信息。
Z
Zombo

如果您有 byte 切片,则可以使用 bytes 包:

package main
import "bytes"

func contains(b []byte, sub byte) bool {
   return bytes.Contains(b, []byte{sub})
}

func main() {
   b := contains([]byte{10, 11, 12, 13, 14}, 13)
   println(b)
}

suffixarray 包:

package main
import "index/suffixarray"

func contains(b []byte, sub byte) bool {
   return suffixarray.New(b).Lookup([]byte{sub}, 1) != nil
}

func main() {
   b := contains([]byte{10, 11, 12, 13, 14}, 13)
   println(b)
}

如果您有 int 切片,则可以使用 intsets 包:

package main
import "golang.org/x/tools/container/intsets"

func main() {
   var s intsets.Sparse
   for n := 10; n < 20; n++ {
      s.Insert(n)
   }
   b := s.Has(16)
   println(b)
}

https://golang.org/pkg/bytes

https://golang.org/pkg/index/suffixarray

https://pkg.go.dev/golang.org/x/tools/container/intsets


n
natenho

有几个 packages that can help,但这个似乎很有希望:

https://github.com/wesovilabs/koazee

var numbers = []int{1, 5, 4, 3, 2, 7, 1, 8, 2, 3}
contains, _ := stream.Contains(7)
fmt.Printf("stream.Contains(7): %v\n", contains)

g
glassonion1

我使用反射包创建了以下包含函数。该函数可用于各种类型,如 int32 或 struct 等。

// Contains returns true if an element is present in a slice
func Contains(list interface{}, elem interface{}) bool {
    listV := reflect.ValueOf(list)

    if listV.Kind() == reflect.Slice {
        for i := 0; i < listV.Len(); i++ {
            item := listV.Index(i).Interface()

            target := reflect.ValueOf(elem).Convert(reflect.TypeOf(item)).Interface()
            if ok := reflect.DeepEqual(item, target); ok {
                return true
            }
        }
    }
    return false
}

contains 函数的用法如下

// slice of int32
containsInt32 := Contains([]int32{1, 2, 3, 4, 5}, 3)
fmt.Println("contains int32:", containsInt32)

// slice of float64
containsFloat64 := Contains([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4)
fmt.Println("contains float64:", containsFloat64)


// slice of struct
type item struct {
    ID   string
    Name string
}
list := []item{
    item{
        ID:   "1",
        Name: "test1",
    },
    item{
        ID:   "2",
        Name: "test2",
    },
    item{
        ID:   "3",
        Name: "test3",
    },
}
target := item{
    ID:   "2",
    Name: "test2",
}
containsStruct := Contains(list, target)
fmt.Println("contains struct:", containsStruct)

// Output:
// contains int32: true
// contains float64: true
// contains struct: true

请在此处查看更多详细信息:https://github.com/glassonion1/xgo/blob/main/contains.go


c
chopper24

它可能被认为有点“hacky”,但根据切片的大小和内容,您可以将切片连接在一起并进行字符串搜索。

例如,您有一个包含单个单词值的切片(例如“yes”、“no”、“maybe”)。这些结果被附加到一个切片中。如果您想检查此切片是否包含任何“可能”结果,您可以使用

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

这实际上有多合适取决于切片的大小和其成员的长度。大切片或长值可能存在性能或适用性问题,但对于有限大小和简单值的较小切片,它是实现所需结果的有效单线。


不适用于元素具有相似文本但不完全相同的情况exSlice := ["yes and no", "maybe", "maybe another"]
这是实现快速而肮脏的单线解决方案的一种相当不错的方法。您只需要一个明确的分隔符(可以是逗号)并做额外的工作来将两个字符串括起来:","+strings.Join(exSlice,",")+","",maybe,"