Ein Stück Schlüssel von einer Karte bekommen

230

Gibt es eine einfachere / schönere Möglichkeit, in Go ein Stück Schlüssel von einer Karte zu erhalten?

Derzeit iteriere ich über die Karte und kopiere die Schlüssel in ein Slice:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Saswat Padhi
quelle
19
Die richtige Antwort ist nein, es gibt keinen einfacheren / schöneren Weg.
dldnh

Antworten:

202

Beispielsweise,

package main

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

Um in Go effizient zu sein, ist es wichtig, die Speicherzuweisungen zu minimieren.

peterSO
quelle
29
Es ist etwas besser, die tatsächliche Größe anstelle der Kapazität festzulegen und das Anhängen insgesamt zu vermeiden. Siehe meine Antwort für Details.
Vinay Pai
3
Beachten Sie, dass wenn dies mymapkeine lokale Variable ist (und daher wächst / schrumpft), dies die einzig richtige Lösung ist - es stellt sicher, dass es keine Ausgänge gibt , wenn sich die Größe der mymapÄnderungen zwischen der Initialisierung keysund der forSchleife ändert. of-bounds Probleme.
Melllvar
12
Karten sind bei gleichzeitigem Zugriff nicht sicher. Keine der beiden Lösungen ist akzeptabel, wenn eine andere Goroutine die Karte ändern könnte.
Vinay Pai
@ VinayPai es ist in Ordnung, von einer Karte von mehreren Goroutinen zu lesen, aber nicht zu schreiben
darethas
@ Darethas das ist ein häufiges Missverständnis. Der Race Detector markiert diese Verwendung seit 1.6. Aus den Versionshinweisen: "Wie immer, wenn eine Goroutine auf eine Karte schreibt, sollte keine andere Goroutine die Karte gleichzeitig lesen oder schreiben. Wenn die Laufzeit diesen Zustand erkennt, druckt sie eine Diagnose und stürzt das Programm ab." golang.org/doc/go1.6#runtime
Vinay Pai
373

Dies ist eine alte Frage, aber hier sind meine zwei Cent. Die Antwort von PeterSO ist etwas prägnanter, aber etwas weniger effizient. Sie wissen bereits, wie groß es sein wird, sodass Sie nicht einmal append verwenden müssen:

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

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

In den meisten Situationen wird es wahrscheinlich keinen großen Unterschied machen, aber es ist nicht viel mehr Arbeit, und in meinen Tests (unter Verwendung einer Karte mit 1.000.000 zufälligen int64Schlüsseln und anschließendes zehnmaliges Generieren des Schlüsselarrays mit jeder Methode) ging es darum 20% schneller, um Mitglieder des Arrays direkt zuzuweisen, als um Anhängen zu verwenden.

Durch das Festlegen der Kapazität werden zwar Neuzuweisungen vermieden, das Anhängen muss jedoch noch zusätzliche Arbeit leisten, um zu überprüfen, ob Sie bei jedem Anhängen die Kapazität erreicht haben.

Vinay Pai
quelle
46
Dies sieht genauso aus wie der OP-Code. Ich stimme zu, dass dies der bessere Weg ist, aber ich bin gespannt, ob ich den Unterschied zwischen dem Code dieser Antwort und dem Code von OP übersehen habe.
Emmaly Wilson
4
Guter Punkt, ich habe mir irgendwie die anderen Antworten angesehen und übersehen, dass meine Antwort genau die gleiche ist wie die des OP. Na ja, zumindest wissen wir jetzt ungefähr, was die Strafe für die unnötige Verwendung von Append ist :)
Vinay Pai
4
Warum verwenden Sie keinen Index mit dem Bereich for i, k := range mymap{? Auf diese Weise brauchen Sie das i ++ nicht?
mvndaai
29
Vielleicht fehlt mir hier etwas, aber wenn Sie es getan haben i, k := range mymap, dann isind es Schlüssel und kWerte, die diesen Schlüsseln in der Karte entsprechen. Das hilft Ihnen nicht wirklich dabei, ein Stück Schlüssel zu füllen.
Vinay Pai
4
@Alaska Wenn Sie sich mit den Kosten für die Zuweisung einer temporären Zählervariablen befassen, aber der Meinung sind, dass ein Funktionsaufruf weniger Speicherplatz beansprucht, sollten Sie sich darüber informieren, was tatsächlich passiert, wenn eine Funktion aufgerufen wird. Hinweis: Es ist keine magische Beschwörung, die Dinge kostenlos macht. Wenn Sie der Meinung sind, dass die aktuell akzeptierte Antwort bei gleichzeitigem Zugriff sicher ist, müssen Sie auch zu den Grundlagen zurückkehren: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai
79

Sie können auch ein Array von Schlüsseln mit Typ []Valuenach MapKeysStrukturmethode Valueaus dem Paket "Reflect" entnehmen :

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
quelle
1
Ich denke, dies ist ein guter Ansatz, wenn die Möglichkeit eines gleichzeitigen Kartenzugriffs besteht: Dies wird nicht in Panik geraten, wenn die Karte während der Schleife wächst. In Bezug auf die Leistung bin ich mir nicht sicher, aber ich vermute, dass sie die Append-Lösung übertrifft.
Atila Romero
@AtilaRomero Sie sind sich nicht sicher, ob diese Lösung Vorteile bietet. Wenn Sie jedoch Reflection für einen beliebigen Zweck verwenden, ist dies nützlicher, da ein Schlüssel als direkt eingegebener Wert verwendet werden kann.
Denis Kreshikhin
10
Gibt es eine Möglichkeit, dies umzuwandeln []string?
Doron Behar
14

Ein besserer Weg, dies zu tun, wäre append:

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

Abgesehen davon haben Sie kein Glück - Go ist keine sehr ausdrucksstarke Sprache.

Rechtsfalte
quelle
10
Es ist jedoch weniger effizient als das Original - append führt mehrere Zuweisungen durch, um das zugrunde liegende Array zu vergrößern, und muss die Slice-Länge bei jedem Aufruf aktualisieren. Das Sprichwort keys = make([]int, 0, len(mymap))wird die Zuweisungen loswerden, aber ich gehe davon aus, dass es immer noch langsamer sein wird.
Nick Craig-Wood
1
Diese Antwort ist sicherer als die Verwendung von len (mymap), wenn jemand anderes die Karte ändert, während die Kopie erstellt wird.
Atila Romero
7

Ich habe einen skizzenhaften Benchmark für die drei in anderen Antworten beschriebenen Methoden erstellt.

Offensichtlich ist die Vorbelegung des Slice vor dem Ziehen der Tasten schneller als die appendEingabe, aber überraschenderweise ist die reflect.ValueOf(m).MapKeys()Methode erheblich langsamer als die letztere:

 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

Hier ist der Code: https://play.golang.org/p/Z8O6a2jyfTH (das Ausführen auf dem Spielplatz bricht ab und behauptet, dass es zu lange dauert, also führen Sie es lokal aus.)

Nico Villanueva
quelle
2
In Ihrer keysAppendFunktion können Sie die Kapazität des keysArrays einstellen make([]uint64, 0, len(m)), wodurch sich die Leistung dieser Funktion für mich drastisch geändert hat.
Keithbhunter
1

Besuchen Sie 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
quelle