Gibt es in Golang eine gute Möglichkeit, ein Stück Werte von einer Karte zu erhalten?

88

Wenn ich eine Karte m habe, gibt es eine bessere Möglichkeit, einen Schnitt der Werte v zu erhalten

package main
import (
  "fmt"
)

func main() {
    m := make(map[int]string)

    m[1] = "a"
    m[2] = "b"
    m[3] = "c"
    m[4] = "d"

    // Can this be done better?
    v := make([]string, len(m), len(m))
    idx := 0
    for  _, value := range m {
       v[idx] = value
       idx++
    }

    fmt.Println(v)
 }

Gibt es eine integrierte Funktion einer Karte? Gibt es eine Funktion in einem Go-Paket oder ist dies der beste Code, wenn ich muss?

masebase
quelle
1
Nennen Sie es anstelle von '_' in Ihrer for-Schleife idx und lassen Sie das idx ++
Peter Agnew,
Nein, er kann nicht, wenn Sie sich über eine Karte erstrecken, gibt sie Schlüssel, Wert nicht Index, Wert zurück. In seinem Beispiel verwendet er 1 als ersten Schlüssel. Dadurch werden die Indizes in Slice v falsch, da der Startindex 1 und nicht Null ist und wenn er 4 erreicht, liegt er außerhalb des Bereichs. play.golang.org/p/X8_SbgxK4VX
Popmedic
@Popmedic Eigentlich kann er ja. ersetzen Sie einfach _mit idxund verwenden , idx-1wenn die Schichtwerte zuweisen.
Hewiefreeman
3
@ newplayer66, das ist ein sehr gefährliches Muster.
Popmedic

Antworten:

59

Unglücklicherweise nicht. Es gibt keine eingebaute Möglichkeit, dies zu tun.

Als Randnotiz können Sie das Kapazitätsargument bei Ihrer Slice-Erstellung weglassen:

v := make([]string, len(m))

Die Kapazität entspricht hier der Länge.

jimt
quelle
Ich denke, es gibt einen besseren Weg: stackoverflow.com/a/61953291/1162217
Lukas Lukac
57

Als Ergänzung zu Jimts Beitrag:

Sie können appenddie Werte auch verwenden, anstatt sie explizit ihren Indizes zuzuweisen:

m := make(map[int]string)

m[1] = "a"
m[2] = "b"
m[3] = "c"
m[4] = "d"

v := make([]string, 0, len(m))

for  _, value := range m {
   v = append(v, value)
}

Beachten Sie, dass die Länge Null ist (noch keine Elemente vorhanden), die Kapazität (zugewiesener Speicherplatz) jedoch mit der Anzahl der Elemente von initialisiert wird m. Dies geschieht, sodass appendnicht jedes Mal Speicher zugewiesen werden muss, wenn die Kapazität des Slice verschöpft ist.

Sie können makedas Slice auch ohne den Kapazitätswert verwenden und appendden Speicher für sich selbst zuweisen lassen .

nemo
quelle
Ich habe mich gefragt, ob dies langsamer sein würde (vorausgesetzt, die Zuweisung erfolgt im Voraus). Ich habe einen groben Benchmark mit einer Karte [int] int durchgeführt und es schien ungefähr 1-2% langsamer zu sein. Irgendwelche Ideen, ob dies etwas ist, worüber man sich Sorgen machen muss oder ob man einfach dazu passt?
Masebase
1
Ich würde annehmen, dass das Anhängen etwas langsamer ist, aber dieser Unterschied ist in den meisten Fällen vernachlässigbar. Benchmark zum Vergleich der direkten Zuordnung und zum Anhängen .
Nemo
1
Mischen Sie dies vorsichtig mit der obigen Antwort. make([]appsv1.Deployment, len(d))Wenn Sie das Array nach der Verwendung anhängen, wird es an eine Reihe leerer Elemente angehängt, die beim Zuweisen leerer Elemente erstellt len(d)wurden.
Anirudh Ramanathan
1

Soweit mir derzeit bekannt ist, gibt es bei go keine Methode zum Verketten von Zeichenfolgen / Bytes zu einer resultierenden Zeichenfolge, ohne mindestens / zwei / Kopien zu erstellen.

Sie müssen derzeit ein [] Byte vergrößern, da alle Zeichenfolgenwerte const sind. DANN müssen Sie die integrierte Zeichenfolge verwenden, damit die Sprache ein 'gesegnetes' Zeichenfolgenobjekt erstellt, für das der Puffer kopiert wird, da irgendwo irgendwo eine Referenz vorhanden sein könnte an die Adresse, die das Byte [] unterstützt.

Wenn ein [] Byte geeignet ist, können Sie einen sehr geringen Vorsprung gegenüber den Bytes erzielen. Verbinden Sie die Funktion, indem Sie eine Zuordnung vornehmen und die Kopie selbst aufrufen.

package main
import (
  "fmt"
)

func main() {
m := make(map[int]string)

m[1] = "a" ;    m[2] = "b" ;     m[3] = "c" ;    m[4] = "d"

ip := 0

/* If the elements of m are not all of fixed length you must use a method like this;
 * in that case also consider:
 * bytes.Join() and/or
 * strings.Join()
 * They are likely preferable for maintainability over small performance change.

for _, v := range m {
    ip += len(v)
}
*/

ip = len(m) * 1 // length of elements in m
r := make([]byte, ip, ip)
ip = 0
for  _, v := range m {
   ip += copy(r[ip:], v)
}

// r (return value) is currently a []byte, it mostly differs from 'string'
// in that it can be grown and has a different default fmt method.

fmt.Printf("%s\n", r)
}
Michael J. Evans
quelle
1

Sie können dieses mapsPaket verwenden:

go get https://github.com/drgrib/maps

Dann müssen Sie nur noch anrufen

values := maps.GetValuesIntString(m)

Es ist typsicher für diese übliche mapKombination. Sie können generateandere typsichere Funktionen für jede andere Art der mapVerwendung von verwendenmapper Tools im selben Paket verwenden.

Vollständige Offenlegung: Ich bin der Schöpfer dieses Pakets. Ich habe es erstellt, weil ich diese Funktionen mapwiederholt neu geschrieben habe.

Chris Redford
quelle
1

Nicht unbedingt besser, aber der sauberere Weg , dies zu tun ist , indem sowohl die trotzt Scheibe LäNGE und Kapazität wietxs := make([]Tx, 0, len(txMap))

    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))

    for _, tx := range txMap {
        txs = append(txs, tx)
    }

Vollständiges Beispiel:

package main

import (
    "github.com/davecgh/go-spew/spew"
)

type Tx struct {
    from  string
    to    string
    value uint64
}

func main() {
    // Extra touch pre-defining the Map length to avoid reallocation
    txMap := make(map[string]Tx, 3)
    txMap["tx1"] = Tx{"andrej", "babayaga", 10}
    txMap["tx2"] = Tx{"andrej", "babayaga", 20}
    txMap["tx3"] = Tx{"andrej", "babayaga", 30}

    txSlice := getTXsAsSlice(txMap)
    spew.Dump(txSlice)
}

func getTXsAsSlice(txMap map[string]Tx) []Tx {
    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))
    for _, tx := range txMap {
        txs = append(txs, tx)
    }

    return txs
}

Einfache Lösung, aber viele Fallstricke. Lesen Sie diesen Blog-Beitrag für weitere Details: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
quelle
Die Antwort ist nicht "falsch". Die Frage verwendet einen Index und keinen Anhang für das Slice. Wenn Sie an das Slice anhängen, ist es schlecht, die Länge vorne festzulegen. Hier ist die Frage, wie sie lautet. Play.golang.org/p/nsIlIl24Irn Zugegeben, diese Frage ist nicht idiomatisch und ich habe noch gelernt. Eine leicht verbesserte Version, von der Sie eher sprechen, ist play.golang.org/p/4SKxC48wg2b
masebase
1
Hallo @masebase, ich halte es für "falsch", weil es besagt: "Leider nein. Es gibt keine eingebaute Möglichkeit, dies zu tun.", Aber es gibt eine "bessere" Lösung, auf die wir beide jetzt 2 Jahre später hinweisen - unter Verwendung des Anhangs () und Definition sowohl der Länge als auch der Kapazität. Aber ich verstehe Ihren Standpunkt, dass es auch nicht richtig ist, es "falsch" zu nennen. Ich werde meinen ersten Satz ändern in: "Nicht unbedingt besser, aber der sauberere Weg, dies zu tun, ist". Ich habe mit ~ 10 Entwicklern gesprochen und alle waren sich einig, dass append () eine sauberere Möglichkeit ist, eine Karte ohne Verwendung eines Hilfsindex in ein Slice zu konvertieren. Das habe ich auch auf dem Weg zum Posting gelernt
Lukas Lukac