Berisi metode untuk irisan

214

Apakah ada yang mirip dengan slice.contains(object)metode di Go tanpa harus melakukan pencarian melalui setiap elemen dalam sebuah slice?

vosmith
sumber

Jawaban:

226

Mostafa telah menunjukkan bahwa metode seperti itu sepele untuk ditulis, dan mkb memberi Anda petunjuk untuk menggunakan pencarian biner dari paket sortir. Tetapi jika Anda akan melakukan banyak pemeriksaan seperti itu, Anda mungkin juga mempertimbangkan untuk menggunakan peta.

Itu sepele untuk memeriksa apakah kunci peta tertentu ada dengan menggunakan value, ok := yourmap[key]idiom. Karena Anda tidak tertarik dengan nilai, Anda mungkin juga membuat map[string]struct{}contoh. Menggunakan kosong di struct{}sini memiliki keuntungan karena tidak memerlukan ruang tambahan dan tipe peta internal Go dioptimalkan untuk nilai-nilai semacam itu. Karena itu, map[string] struct{}merupakan pilihan populer untuk set di dunia Go.

tux21b
sumber
27
Perhatikan juga, bahwa Anda harus menulis struct{}{}untuk mendapatkan nilai struct kosong sehingga Anda dapat meneruskannya ke peta saat Anda ingin menambahkan elemen. Coba saja, dan jika Anda menemukan masalah, jangan ragu untuk bertanya. Anda juga dapat menggunakan solusi Mostafa jika itu lebih mudah bagi Anda untuk memahami (kecuali jika Anda memiliki data dalam jumlah besar).
tux21b
5
Solusi itu sederhana, itu benar. Tetapi apa yang diperlukan untuk menambahkan fungsi dasar seperti itu ke dalam runtime? Saya belum menemukan masalah seperti itu di Go repo on github. Itu menyedihkan dan aneh.
Igor Petrov
1
Bagaimana cara map[string] boolmembandingkan dengan map[string] struct{}. map[string] struct{}sepertinya hack terutama menginisialisasi struct kosongstruct {}{}
vadasambar
@IgorPetrov setuju, saya terkejut fitur dasar seperti itu belum ada di runtime.
jcollum
180

Tidak, metode seperti itu tidak ada, tetapi sepele untuk ditulis:

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

Anda dapat menggunakan peta jika pencarian itu merupakan bagian penting dari kode Anda, tetapi peta juga memiliki biaya.

Mostafa
sumber
257
Sebenarnya ini tidak sepele, karena Anda harus menulis satu untuk setiap jenis yang Anda gunakan, dan karena tidak ada overloading, Anda harus memberi nama setiap fungsi secara berbeda, seperti dalam C. append () dapat bekerja secara umum karena memiliki dukungan runtime khusus. Isi generik akan berguna untuk alasan yang sama, tetapi sebenarnya solusi generik hanyalah dukungan generik dalam bahasa tersebut.
Eloff
15
@ Eloffinterface{}
Alex Lockwood
2
@Alex Lockwood apakah ini benar-benar berfungsi dengan antarmuka?
Ory Band
101
trivial == 7 baris kode termasuk 1 loop 1 cabang jika pernyataan dan 1 perbandingan? Saya pikir saya kehilangan sesuatu di sini ...
tothemario
3
Tapi mengapa tidak menambahkan ini sendiri?
Luna Lovegood
12

Alih-alih menggunakan slice,map mungkin solusi yang lebih baik.

contoh sederhana:

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
sumber
34
Dalam bentuknya yang sekarang, kode ini tidak menawarkan manfaat, karena tidak ada gunanya membuat peta dari sebuah slice jika Anda hanya akan menggunakannya sekali. - Agar bermanfaat, kode ini sebaiknya menyediakan fungsi sliceToMapyang melakukan semua persiapan. Setelah itu, menanyakan peta itu sepele dan efisien.
Roland Illig
9

The semacam paket menyediakan blok bangunan jika slice Anda disortir atau Anda bersedia untuk mengatasinya.

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
}

SearchStringberjanji untuk kembali the index to insert x if x is not present (it could be len(a)), jadi cek yang mengungkapkan apakah string berisi slice yang diurutkan.

Henrik Aasted Sørensen
sumber
Dalam hal waktu, pencarian reguler adalah O(n)dan solusi ini membuatnya O(n*log(n)).
plesiv
@plesiv ini pencarian biner, AFAICS. Bukankah itu membuatnya O (log n)?
Henrik Aasted Sørensen
ya, biner-pencarian dan fungsi containsyang O(log(n)), tapi pendekatan secara keseluruhan O(n*log(n))karena semacam itu.
plesiv
3

Anda bisa menggunakan paket refleksi untuk beralih di atas antarmuka yang tipe konkretnya adalah slice:

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
sumber
3
Tentu Anda dapat menggunakan paket refleksi tetapi hanya karena Anda bisa, tidak berarti Anda harus melakukannya. Refleksi sangat mahal.
Justin Ohms
3

Jika tidak layak menggunakan peta untuk menemukan item berdasarkan kunci, Anda dapat mempertimbangkan alat yang mematikan . Goderive menghasilkan implementasi khusus tipe berisi metode, membuat kode Anda mudah dibaca dan efisien.

Contoh;

type Foo struct {
    Field1 string
    Field2 int
} 

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

Untuk menghasilkan metode deriveContainsFoo:

  • Instal goderive dengan go get -u github.com/awalterschulze/goderive
  • Jalankan goderive ./...di folder ruang kerja Anda

Metode ini akan dihasilkan untuk deriveContains:

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

Goderive memiliki dukungan untuk beberapa metode penolong bermanfaat lainnya untuk menerapkan gaya pemrograman fungsional.

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

Tidak yakin obat generik diperlukan di sini. Anda hanya perlu kontrak untuk perilaku yang Anda inginkan. Melakukan hal berikut tidak lebih dari apa yang harus Anda lakukan dalam bahasa lain jika Anda ingin objek Anda sendiri berperilaku dalam koleksi, dengan menimpa Equals () dan GetHashCode () misalnya.

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
sumber
1
"tidak lebih dari apa yang harus Anda lakukan dalam bahasa lain" tidak benar - misalnya dalam C # Contains()diimplementasikan List<T>, jadi Anda hanya perlu menerapkan Equals()untuk pekerjaan itu.
George
1

Saya membuat tolok ukur yang sangat sederhana dengan solusi dari jawaban ini.

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

Ini bukan tolok ukur yang nyata karena pada awalnya, saya belum memasukkan terlalu banyak elemen tetapi merasa ragu untuk memotong dan mengubahnya.

F. Norbert
sumber
Saya memikirkannya tetapi tidak terlalu representatif karena mesin saya tidak begitu kuat.
F. Norbert
0

Mungkin dianggap sedikit 'retas' tetapi tergantung pada ukuran dan isi slice, Anda dapat menggabungkan slice bersama-sama dan melakukan pencarian string.

Misalnya Anda memiliki slice yang berisi nilai kata tunggal (mis. "Ya", "tidak", "mungkin"). Hasil ini ditambahkan ke irisan. Jika Anda ingin memeriksa apakah irisan ini berisi hasil "mungkin", Anda dapat menggunakan

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

Seberapa cocok ini benar-benar tergantung pada ukuran irisan dan panjang anggotanya. Mungkin ada masalah kinerja atau kesesuaian untuk irisan besar atau nilai panjang, tetapi untuk irisan kecil ukuran terbatas dan nilai sederhana itu adalah satu-liner yang valid untuk mencapai hasil yang diinginkan.

chopper24
sumber
Tidak akan berfungsi untuk situasi di mana elemen memiliki teks yang mirip tetapi tidak persis samaexSlice := ["yes and no", "maybe", "maybe another"]
Raees Iqbal
Ini adalah pendekatan yang agak bagus untuk mencapai solusi one-liner cepat dan kotor. Anda hanya perlu membutuhkan pembatas yang tidak ambigu (bisa berupa koma) dan melakukan pekerjaan ekstra untuk mengurung kedua string ","+strings.Join(exSlice,",")+","",maybe,"
:,
-1

Gaya bepergian:

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
sumber
2
Ini tidak menjawab pertanyaan, juga tidak memberikan informasi tambahan.
Croolman