slice.contains(object)
スライス内の各要素を検索せずにGoのメソッドに似たものはありますか?
18 に答える
Mostafaは、そのようなメソッドを書くのは簡単であるとすでに指摘しており、mkbは、ソートパッケージからのバイナリ検索を使用するためのヒントを提供しました。ただし、このような包含チェックを多数実行する場合は、代わりにマップの使用を検討することもできます。
value, ok := yourmap[key]
イディオムを使用して、特定のマップキーが存在するかどうかを確認するのは簡単です。値に興味がないので、map[string]struct{}
たとえばを作成することもできます。ここで空を使用するstruct{}
と、追加のスペースが不要であり、Goの内部マップタイプがその種類の値に最適化されているという利点があります。したがって、map[string] struct{}
Goの世界でセットに人気のある選択肢です。
いいえ、そのような方法は存在しませんが、次のように書くのは簡単です。
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
そのルックアップがコードの重要な部分である場合はマップを使用できますが、マップにもコストがかかります。
ソートパッケージは、スライスがソートされている場合、またはスライスをソートする意思がある場合に、ビルディングブロックを提供します。
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))
いるので、それをチェックすると、文字列にソートされたスライスが含まれているかどうかがわかります。
を使用する代わりにslice
、map
より良い解決策かもしれません。
簡単な例:
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))
}
スライスがソートされている場合、sort
パッケージに実装されているバイナリ検索があります。
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
}
Go 1.18+では、ジェネリックを使用できます。
func Contains[T comparable](s []T, e T) bool {
for _, v := range s {
if v == e {
return true
}
}
return false
}
リフレクトパッケージを使用して、具象タイプがスライスであるインターフェイスを反復処理できます。
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
}
ここでジェネリックが必要かどうかはわかりません。あなたはただあなたの望む行動のための契約が必要です。たとえば、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
}
キーに基づいてアイテムを見つけるためにマップを使用することが不可能な場合は、goderiveツールを検討できます。Goderiveは、containsメソッドのタイプ固有の実装を生成し、コードを読みやすく効率的にします。
例;
type Foo struct {
Field1 string
Field2 int
}
func Test(m Foo) bool {
var allItems []Foo
return deriveContainsFoo(allItems, m)
}
deriveContainsFooメソッドを生成するには:
- goderiveをインストールする
go get -u github.com/awalterschulze/goderive
goderive ./...
ワークスペースフォルダーで実行
このメソッドは、deriveContainsに対して生成されます。
func deriveContainsFoo(list []Foo, item Foo) bool {
for _, v := range list {
if v == item {
return true
}
}
return false
}
Goderiveは、関数型プログラミングスタイルを外出先で適用するためのその他の便利なヘルパーメソッドをサポートしています。
map[x]bool
よりも便利だと思いますmap[x]struct{}
。
存在しないアイテムのマップにインデックスを付けると、が返されfalse
ます。したがって、の代わりに_, ok := m[X]
、と言うことができますm[X]
。
これにより、式に包含テストを簡単にネストできます。
これらの回答から得られたソリューションを使用して、非常に単純なベンチマークを作成しました。
https://gist.github.com/NorbertFenk/7bed6760198800207e84f141c41d93c7
最初はあまり多くの要素を挿入していなかったので、これは実際のベンチマークではありませんが、自由にフォークして変更してください。
役立つパッケージがいくつかありますが、これは有望なようです。
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)
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 1compatiblePromise™にバインドされておらず、stdlibに正式に追加される前に変更される可能性があることに注意してください。
ゴースタイル:
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"
})
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)
}
リフレクトパッケージを使用して、次のContains関数を作成しました。この関数は、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
少し「ハッキー」と見なされるかもしれませんが、スライスのサイズと内容によっては、スライスを結合して文字列検索を実行できます。
たとえば、単一の単語の値を含むスライスがあります(たとえば、「yes」、「no」、「maybe」)。これらの結果はスライスに追加されます。このスライスに「たぶん」の結果が含まれているかどうかを確認したい場合は、
exSlice := ["yes", "no", "yes", "maybe"]
if strings.Contains(strings.Join(exSlice, ","), "maybe") {
fmt.Println("We have a maybe!")
}
これが実際にどれだけ適切かは、スライスのサイズとそのメンバーの長さによって異なります。大きなスライスまたは長い値にはパフォーマンスまたは適合性の問題がある可能性がありますが、有限サイズおよび単純な値の小さなスライスの場合、目的の結果を達成するための有効なワンライナーです。