En golang, y a-t-il un bon moyen d'obtenir une tranche de valeurs d'une carte?

89

Si j'ai une carte m, y a-t-il un meilleur moyen d'obtenir une tranche des valeurs v alors

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

Y a-t-il une fonction intégrée d'une carte? Y a-t-il une fonction dans un package Go, ou est-ce le meilleur code à faire si je dois le faire?

masebase
la source
1
au lieu de '_' dans votre boucle for, appelez-le idx et abandonnez l'activité idx ++
Peter Agnew
Non, il ne peut pas, lorsque vous passez sur une carte, il renvoie la clé, la valeur pas l'index, la valeur. Dans son exemple, il utilise 1 comme première clé et cela rendra les index de la tranche v incorrects car l'index de départ sera 1 et non zéro, et quand il arrivera à 4, il sera hors de portée. play.golang.org/p/X8_SbgxK4VX
Popmedic
@Popmedic En fait, oui il le peut. remplacez simplement _par idxet utilisez idx-1lors de l'affectation des valeurs de tranche.
hewiefreeman
3
@ newplayer66, c'est un schéma très dangereux.
Popmedic

Réponses:

60

Malheureusement non. Il n'y a aucun moyen intégré de faire cela.

En remarque, vous pouvez omettre l'argument de capacité lors de la création de votre tranche:

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

La capacité est implicitement la même que la longueur ici.

Jimt
la source
Je pense qu'il existe un meilleur moyen: stackoverflow.com/a/61953291/1162217
Lukas Lukac
58

En complément du message de Jimt:

Vous pouvez également utiliser appendplutôt qu'affecter explicitement les valeurs à leurs indices:

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

Notez que la longueur est nulle (aucun élément n'est encore présent) mais la capacité (espace alloué) est initialisée avec le nombre d'éléments de m. Ceci est fait, il appendn'est donc pas nécessaire d'allouer de la mémoire chaque fois que la capacité de la tranche vest épuisée.

Vous pouvez également makela tranche sans la valeur de capacité et laisser appendallouer la mémoire pour elle-même.

nemo
la source
Je me demandais si ce serait plus lent (en supposant une allocation initiale)? J'ai fait un benchmark grossier avec une map [int] int et il m'a semblé environ 1-2% plus lent. Avez-vous des idées si c'est quelque chose à craindre ou si cela va de pair?
masebase
1
Je suppose que l'append est un peu plus lent, mais cette différence est, dans la plupart des cas, négligeable. Benchmark comparant l'affectation directe et l'ajout .
nemo
1
Faites attention en mélangeant cela avec la réponse ci-dessus - l'ajout au tableau après l'utilisation make([]appsv1.Deployment, len(d))ajoutera à un tas d'éléments vides qui ont été créés lorsque vous avez alloué len(d)des éléments vides.
Anirudh Ramanathan
1

Pour autant que je sache actuellement, go n'a pas de méthode pour concaténer des chaînes / octets dans une chaîne résultante sans faire au moins / deux / copies.

Vous devez actuellement augmenter un [] octet puisque toutes les valeurs de chaîne sont const, ALORS vous devez utiliser la chaîne intégrée pour que le langage crée un objet chaîne 'béni', pour lequel il copiera le tampon car quelque chose quelque part pourrait avoir une référence à l'adresse sauvegardant l'octet [].

Si un octet [] convient alors vous pouvez gagner une très légère avance sur la fonction bytes.Join en faisant une allocation et en faisant la copie vous-même.

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
la source
1

Vous pouvez utiliser ce mapspackage:

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

Alors tout ce que tu as à appeler c'est

values := maps.GetValuesIntString(m)

Il est sans danger pour cette mapcombinaison courante . Vous pouvez utiliser d' generateautres fonctions de type sécurisé pour tout autre type d' maputilisation de l' mapperoutil dans le même package.

Divulgation complète: je suis le créateur de ce package. Je l'ai créé parce que je me suis retrouvé à réécrire ces fonctions à mapplusieurs reprises.

Chris Redford
la source
1

Pas nécessairement mieux, mais la façon la plus propre de le faire est de définir à la fois la LONGUEUR de la tranche et la CAPACITÉ commetxs := 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)
    }

Exemple complet:

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
}

Solution simple mais beaucoup de pièges. Lisez cet article de blog pour plus de détails: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
la source
La réponse n'est pas «fausse». La question utilise un index et aucun ajout pour la tranche. Si vous utilisez append sur la tranche, alors oui, définir la longueur à l'avant serait mauvais. Voici la question telle qu'elle est play.golang.org/p/nsIlIl24Irn Certes, cette question n'est pas idiomatique aller et j'apprenais encore. Une version légèrement améliorée qui ressemble plus à ce dont vous parlez est play.golang.org/p/4SKxC48wg2b
masebase
1
Salut @masebase, je considère que c'est "faux" car il dit: "Malheureusement, non. Il n'y a pas de moyen intégré de le faire.", Mais il existe une "meilleure" solution que nous soulignons tous les deux maintenant 2 ans plus tard - en utilisant l'append () et définissant à la fois la longueur et la capacité. Mais je vois votre argument selon lequel l'appeler «faux» n'est pas non plus exact. Je changerai ma première phrase en: "Pas forcément mieux, mais la façon la plus propre de le faire est". J'ai parlé à environ 10 développeurs et tout le monde a convenu que append () est un moyen plus propre de convertir une carte en une tranche sans utiliser d'index d'aide. J'ai appris cela aussi en postant
Lukas Lukac