Mendapatkan sepotong kunci dari peta

230

Apakah ada cara yang lebih sederhana / lebih baik untuk mendapatkan sepotong kunci dari peta di Go?

Saat ini saya mengulangi peta dan menyalin kunci ke sebuah irisan:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Saswat Padhi
sumber
19
Jawaban yang benar adalah tidak, tidak ada cara yang lebih sederhana / lebih bagus.
dldnh

Jawaban:

202

Sebagai contoh,

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

Agar efisien di Go, penting untuk meminimalkan alokasi memori.

peterSO
sumber
29
Sedikit lebih baik untuk mengatur ukuran sebenarnya daripada kapasitas dan menghindari menambahkan sama sekali. Lihat jawaban saya untuk detailnya.
Vinay Pai
3
Perhatikan bahwa jika mymapbukan variabel lokal (dan karena itu tunduk pada tumbuh / menyusut), ini adalah satu-satunya solusi yang tepat - itu memastikan bahwa jika ukuran mymapperubahan antara inisialisasi keysdan forloop, tidak akan ada keluaran masalah-masalah di luar batas.
Melllvar
12
peta tidak aman di bawah akses bersamaan, tidak ada solusi yang dapat diterima jika goroutine lain dapat mengubah peta.
Vinay Pai
@VinayPai tidak apa-apa untuk membaca dari peta dari beberapa goroutine tetapi tidak menulis
darethas
@darethas itu adalah kesalahpahaman umum. Detektor ras akan menandai penggunaan ini sejak 1.6. Dari catatan rilis: "Seperti biasa, jika satu goroutine menulis ke peta, tidak ada goroutine lain yang membaca atau menulis peta secara bersamaan. Jika runtime mendeteksi kondisi ini, ia mencetak diagnosa dan membuat crash program." golang.org/doc/go1.6#runtime
Vinay Pai
375

Ini pertanyaan lama, tapi ini dua sen saya. Jawaban PeterSO sedikit lebih ringkas, tetapi sedikit kurang efisien. Anda sudah tahu seberapa besar itu sehingga Anda bahkan tidak perlu menggunakan append:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

Dalam kebanyakan situasi mungkin tidak akan membuat banyak perbedaan, tapi itu tidak banyak bekerja, dan dalam pengujian saya (menggunakan peta dengan 1.000.000 int64kunci acak dan kemudian menghasilkan array kunci sepuluh kali dengan masing-masing metode), itu tentang 20% lebih cepat untuk menetapkan anggota array secara langsung daripada menggunakan append.

Meskipun pengaturan kapasitas menghilangkan realokasi, append masih harus melakukan pekerjaan ekstra untuk memeriksa apakah Anda telah mencapai kapasitas pada setiap append.

Vinay Pai
sumber
46
Ini terlihat persis sama dengan kode OP. Saya setuju bahwa ini adalah cara yang lebih baik, tetapi saya ingin tahu apakah saya melewatkan perbedaan antara kode jawaban ini dan kode OP.
Emmaly Wilson
4
Poin bagusnya, entah bagaimana saya melihat jawaban yang lain dan melewatkan jawaban saya persis sama dengan OP. Oh well, setidaknya kita sekarang tahu kira-kira apa hukumannya jika tidak perlu menggunakan append :)
Vinay Pai
5
Mengapa Anda tidak menggunakan indeks dengan rentang for i, k := range mymap{,. Dengan begitu Anda tidak perlu i ++?
mvndaai
30
Mungkin saya kehilangan sesuatu di sini, tetapi jika Anda melakukannya i, k := range mymap, maka iakan menjadi kunci dan kakan menjadi nilai yang sesuai dengan kunci tersebut di peta. Itu tidak akan benar-benar membantu Anda mengisi sepotong kunci.
Vinay Pai
4
@Laska jika Anda khawatir dengan biaya mengalokasikan satu variabel penghitung sementara, tetapi berpikir pemanggilan fungsi akan mengambil lebih sedikit memori, Anda harus mendidik diri sendiri tentang apa yang sebenarnya terjadi ketika suatu fungsi dipanggil. Petunjuk: Ini bukan mantra ajaib yang melakukan hal-hal gratis. Jika menurut Anda jawaban yang diterima saat ini aman di bawah akses bersamaan, Anda juga perlu kembali ke dasar-dasarnya: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai
79

Anda juga dapat mengambil larik kunci dengan jenis []Valuemenurut metode MapKeysstruct Valuedari paket "mencerminkan":

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}
Denis Kreshikhin
sumber
1
Saya pikir ini adalah pendekatan yang baik jika ada kemungkinan akses peta bersamaan: ini tidak akan panik jika peta tumbuh selama loop. Tentang kinerja, saya tidak begitu yakin, tapi saya curiga ini mengungguli solusi tambahan.
Atila Romero
@AtilaRomero Tidak yakin bahwa solusi ini memiliki kelebihan, tetapi ketika menggunakan refleksi untuk tujuan apa pun, ini lebih berguna, karena memungkinkan untuk mengambil kunci ketika Value diketik langsung.
Denis Kreshikhin
10
Apakah ada cara untuk mengubahnya []string?
Doron Behar
14

Cara yang lebih baik untuk melakukan ini adalah dengan menggunakan append:

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

Selain itu, Anda kurang beruntung — Go bukan bahasa yang sangat ekspresif.

sayap kanan
sumber
10
Ini kurang efisien daripada yang asli - append akan melakukan alokasi ganda untuk menumbuhkan array yang mendasarinya, dan harus memperbarui panjang irisan setiap panggilan. Mengatakan keys = make([]int, 0, len(mymap))akan menyingkirkan alokasi tetapi saya berharap itu akan lebih lambat.
Nick Craig-Wood
1
Jawaban ini lebih aman daripada menggunakan len (mymap), jika orang lain mengubah peta saat salinan dibuat.
Atila Romero
7

Saya membuat patokan samar pada tiga metode yang dijelaskan dalam tanggapan lain.

Jelas pra-alokasi potongan sebelum menarik kunci lebih cepat daripada appending, tetapi mengejutkan, reflect.ValueOf(m).MapKeys()metode ini jauh lebih lambat daripada yang terakhir:

 go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

Berikut kodenya: https://play.golang.org/p/Z8O6a2jyfTH (menjalankannya di taman bermain batal dengan mengklaim bahwa terlalu lama, jadi, jalankan secara lokal.)

Nico Villanueva
sumber
2
Dalam keysAppendfungsi Anda, Anda dapat mengatur kapasitas keysarray make([]uint64, 0, len(m)), yang secara drastis mengubah kinerja fungsi tersebut untuk saya.
keithbhunter
1

Kunjungi https://play.golang.org/p/dx6PTtuBXQW

package main

import (
    "fmt"
    "sort"
)

func main() {
    mapEg := map[string]string{"c":"a","a":"c","b":"b"}
    keys := make([]string, 0, len(mapEg))
    for k := range mapEg {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    fmt.Println(keys)
}
Lalit Sharma
sumber