Haritadan bir dilim anahtar alma


230

Go'daki bir haritadan bir dilim anahtar almanın daha basit / daha güzel bir yolu var mı?

Şu anda harita üzerinde yineleme ve bir dilim anahtarları kopyalama:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
Doğru cevap hayır, daha basit / daha güzel bir yol yok.
dldnh

Yanıtlar:


203

Örneğin,

package main

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

Go'da verimli olmak için bellek ayırmalarını en aza indirmek önemlidir.


29
Kapasite yerine gerçek boyutu ayarlamak ve tamamen eklemekten kaçınmak biraz daha iyidir. Ayrıntılar için cevabıma bakın.
Vinay Pai

3
mymapYerel bir değişken değilse (ve bu nedenle büyümeye / daralmaya maruz kalıyorsa ), bunun tek uygun çözüm olduğunu unutmayın - mymapbaşlatma keysve fordöngü arasındaki değişikliklerin boyutunun herhangi bir çıkış olmayacağını garanti eder. sınırları aşan sorunlar.
Melllvar

12
eşzamanlı erişim altında haritalar güvenli değildir, başka bir goroutin haritayı değiştirebilirse her iki çözüm de kabul edilemez.
Vinay Pai

@VinayPai, bir haritadan birden fazla goroutinden okumak ama yazmak değil
darethas

@darethas bu yaygın bir yanlış anlamadır. Yarış detektörü 1.6'dan beri bu kullanımı işaretleyecektir. Sürüm notlarından: "Her zaman olduğu gibi, bir gorout bir haritaya yazıyorsa, başka bir goroutin haritayı aynı anda okumalı veya yazmamalıdır. Çalışma zamanı bu durumu algılarsa, bir tanı yazdırır ve programı kilitler." golang.org/doc/go1.6#runtime
Vinay Pai

375

Bu eski bir soru, ama işte benim iki sentim. PeterSO'nun cevabı biraz daha özlü, ancak biraz daha az verimli. Ne kadar büyük olacağını zaten biliyorsunuz, bu yüzden append kullanmanıza bile gerek yok:

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

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

Çoğu durumda, muhtemelen çok fazla bir fark yaratmaz, ancak daha fazla iş değildir ve testlerimde (1.000.000 rastgele bir harita kullanarak) int64 anahtar ve daha sonra her yöntemle on kez anahtar dizisi oluşturmak), Dizinin üyelerini atamaktan doğrudan atamaktan% 20 daha hızlı.

Kapasitenin ayarlanması yeniden tahsisleri ortadan kaldırsa da, append'in her bir append'in kapasitesine ulaşıp ulaşmadığınızı kontrol etmek için ekstra iş yapması gerekir.


46
Bu OP koduyla tamamen aynı görünüyor. Bunun daha iyi bir yol olduğunu kabul ediyorum, ancak bu yanıtın kodu ile OP'nin kodu arasındaki farkı kaçırıp kaçırmadığımı merak ediyorum.
Emmaly Wilson

4
İyi bir nokta, bir şekilde diğer cevaplara baktım ve cevabımın OP ile tamamen aynı olduğunu özledim. Oh, en azından artık gereksiz yere ekleme kullanmak için cezanın ne olduğunu biliyoruz :)
Vinay Pai

5
Neden bu aralıkta bir dizin kullanmıyorsunuz for i, k := range mymap{? Bu şekilde i ++ 'a ihtiyacınız yok mu?
mvndaai

30
Belki burada bir şey eksik, ama eğer öyleyse i, k := range mymap, o izaman anahtar kolacak ve haritadaki bu anahtarlara karşılık gelen değerler olacaktır. Bu aslında bir dilim anahtarı doldurmanıza yardımcı olmaz.
Vinay Pai

4
@Alaska, bir geçici sayaç değişkeni tahsis etme maliyetiyle ilgileniyorsanız, ancak bir işlev çağrısının daha az bellek alacağını düşünüyorsanız, bir işlev çağrıldığında gerçekte neler olduğu konusunda kendinizi eğitmelisiniz. İpucu: İşleri ücretsiz yapan büyülü bir büyü değil. Şu anda kabul edilen cevabın eşzamanlı erişim altında güvenli olduğunu düşünüyorsanız, temel bilgilere de geri dönmeniz gerekir: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai

79

Ayrıca "reflektör" paketindeki yapı []Valueyöntemine MapKeysgöre türe sahip bir anahtar dizisi de alabilirsiniz Value:

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]
}

1
Bence eşzamanlı bir harita erişim şansı varsa bu iyi bir yaklaşımdır: harita döngü sırasında büyürse bu panik olmaz. Performans hakkında, gerçekten emin değilim, ancak bunun ekleme çözümünden daha iyi performans gösterdiğinden şüpheleniyorum.
Atila Romero

@AtilaRomero Bu çözümün herhangi bir avantajı olduğundan emin değilsiniz, ancak yansımayı herhangi bir amaç için kullanırken bu daha kullanışlıdır, çünkü doğrudan girilen Değer olarak bir anahtarın alınmasına izin verir.
Denis Kreshikhin

10
Bunu dönüştürmenin bir yolu var mı []string?
Doron Behar

14

Bunu yapmanın daha güzel bir yolu append:

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

Bunun dışında şansınız kalmadı - Go çok etkileyici bir dil değil.


10
Yine de orijinalden daha az verimlidir - append, altta yatan diziyi büyütmek için birden fazla ayırma yapacaktır ve her çağrıda dilim uzunluğunu güncellemek zorundadır. Söylemek keys = make([]int, 0, len(mymap))tahsislerden kurtulacak ama yine de daha yavaş olacağını umuyorum.
Nick Craig-Wood

1
Kopya yanıtlanırken başkası haritayı değiştirirse, bu cevap len (mymap) kullanmaktan daha güvenlidir.
Atila Romero

7

Diğer yanıtlarda açıklanan üç yöntem üzerinde kabataslak bir karşılaştırma yaptım.

Açıkça, tuşları çekmeden önce dilimi önceden ayırmak ing'den daha hızlıdır append, ancak şaşırtıcı bir şekilde reflect.ValueOf(m).MapKeys()yöntem, ikincisinden önemli ölçüde daha yavaştır:

 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

İşte kod: https://play.golang.org/p/Z8O6a2jyfTH (oyun alanında çalıştırmak çok uzun sürdüğünü iddia ederek iptal ediyor, bu yüzden, yerel olarak çalıştırın.)


2
Senin içinde keysAppendişlev sen kapasitesini ayarlayabilirsiniz keysile dizide make([]uint64, 0, len(m))büyük ölçüde benim için fonksiyonun performansını değiştirdi.
keithbhunter

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.