Obtenir une tranche de clés d'une carte

256

Existe-t-il un moyen plus simple / plus agréable d'obtenir une tranche de clés d'une carte dans Go?

Actuellement, je suis en train d'itérer sur la carte et de copier les clés dans une tranche:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Saswat Padhi
la source
27
La bonne réponse est non, il n'y a pas de moyen plus simple / plus agréable.
dldnh

Réponses:

212

Par exemple,

package main

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

Pour être efficace dans Go, il est important de minimiser les allocations de mémoire.

peterSO
la source
31
Il est légèrement préférable de définir la taille réelle au lieu de la capacité et d'éviter tout ajout. Voir ma réponse pour plus de détails.
Vinay Pai
3
Notez que si ce mymapn'est pas une variable locale (et est donc sujette à croître / rétrécir), c'est la seule solution appropriée - cela garantit que si la taille des mymapchangements entre l'initialisation de keyset la forboucle, il n'y aura pas de sortie problèmes de délimitation.
Melllvar
13
les cartes ne sont pas sûres en cas d'accès simultané, aucune des deux solutions n'est acceptable si un autre goroutine peut changer la carte.
Vinay Pai
@VinayPai, il est correct de lire à partir d'une carte à partir de plusieurs goroutines mais pas d'écrire
darethas
2
@darethas c'est une idée fausse courante. Le détecteur de course signalera cette utilisation depuis 1.6. D'après les notes de publication: "Comme toujours, si un goroutine écrit sur une carte, aucun autre goroutine ne doit lire ou écrire la carte simultanément. Si le moteur d'exécution détecte cette condition, il imprime un diagnostic et plante le programme." golang.org/doc/go1.6#runtime
Vinay Pai
402

C'est une vieille question, mais voici mes deux cents. La réponse de PeterSO est légèrement plus concise, mais légèrement moins efficace. Vous savez déjà quelle sera sa taille, vous n'avez même pas besoin d'utiliser append:

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

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

Dans la plupart des situations, cela ne fera probablement pas beaucoup de différence, mais ce n'est pas beaucoup plus de travail, et dans mes tests (en utilisant une carte avec 1000000 de int64clés aléatoires , puis en générant le tableau de clés dix fois avec chaque méthode), il s'agissait de 20% plus rapide pour affecter les membres du tableau directement que pour utiliser append.

Bien que la définition de la capacité élimine les réallocations, append doit encore effectuer un travail supplémentaire pour vérifier si vous avez atteint la capacité à chaque ajout.

Vinay Pai
la source
50
Cela ressemble exactement au code de l'OP. Je conviens que c'est la meilleure façon, mais je suis curieux de savoir si j'ai manqué la différence entre le code de cette réponse et le code de l'OP.
Emmaly Wilson
5
Bon point, j'ai en quelque sorte regardé les autres réponses et j'ai manqué que ma réponse est exactement la même que le PO. Eh bien, au moins, nous savons maintenant à peu près quelle est la pénalité pour utiliser append inutilement :)
Vinay Pai
5
Pourquoi ne vous utilisez un index avec la gamme, for i, k := range mymap{. De cette façon, vous n'avez pas besoin du i ++?
mvndaai
33
Il me manque peut-être quelque chose ici, mais si vous l'avez fait i, k := range mymap, ce iseront des clés et des kvaleurs correspondant à ces clés dans la carte. Cela ne vous aidera pas réellement à remplir une tranche de clés.
Vinay Pai
4
@Alaska si vous êtes préoccupé par le coût de l'allocation d'une variable de compteur temporaire, mais pensez qu'un appel de fonction va prendre moins de mémoire, vous devriez vous renseigner sur ce qui se passe réellement lorsqu'une fonction est appelée. Indice: ce n'est pas une incantation magique qui fait les choses gratuitement. Si vous pensez que la réponse actuellement acceptée est sûre en accès simultané, vous devez également revenir aux bases: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai
84

Vous pouvez également prendre un tableau de clés avec type []Valuepar méthode MapKeysde struct à Valuepartir du package "refléter":

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
la source
1
Je pense que c'est une bonne approche s'il y a une chance d'accès simultané à la carte: cela ne paniquera pas si la carte grandit pendant la boucle. À propos des performances, je ne suis pas vraiment sûr, mais je soupçonne que cela surpasse la solution d'ajout.
Atila Romero
@AtilaRomero Pas sûr que cette solution présente des avantages, mais lorsque vous utilisez la réflexion à quelque fin que ce soit, cela est plus utile, car cela permet de prendre une clé comme valeur tapée directement.
Denis Kreshikhin
14
Existe-t-il un moyen de le convertir en []string?
Doron Behar
1
@AtilaRomero Je viens de le tester, ça panique encore quand la liste s'allonge.
gtato
Oh, bon à savoir alors.
Atila Romero
13

Une meilleure façon de le faire serait d'utiliser append:

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

À part cela, vous n'avez pas de chance - Go n'est pas un langage très expressif.

à droite
la source
10
Cependant, il est moins efficace que l'original - append effectuera plusieurs allocations pour agrandir le tableau sous-jacent et devra mettre à jour la longueur de la tranche à chaque appel. Dire keys = make([]int, 0, len(mymap))va supprimer les allocations, mais je pense que ce sera encore plus lent.
Nick Craig-Wood
1
Cette réponse est plus sûre que d'utiliser len (mymap), si quelqu'un d'autre modifie la carte pendant que la copie est en cours de réalisation.
Atila Romero
13

J'ai fait un point de référence sommaire sur les trois méthodes décrites dans d'autres réponses.

Évidemment, pré-allouer la tranche avant de tirer les touches est plus rapide que appending, mais étonnamment, la reflect.ValueOf(m).MapKeys()méthode est nettement plus lente que cette dernière:

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

Voici le code: https://play.golang.org/p/Z8O6a2jyfTH (l'exécuter dans le terrain de jeu s'interrompt en affirmant que cela prend trop de temps, alors, eh bien, exécutez-le localement.)

Nico Villanueva
la source
2
Dans votre keysAppendfonction, vous pouvez définir la capacité de la keysbaie avec make([]uint64, 0, len(m)), ce qui a radicalement changé les performances de cette fonction pour moi.
keithbhunter
2

Visitez 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
la source
Une réponse complète +1.
Tom L le