Trier les valeurs de la carte Go par clés

94

Lors de l'itération de la carte retournée dans le code, renvoyée par la fonction de rubrique, les touches n'apparaissent pas dans l'ordre.

Comment puis-je faire en sorte que les clés soient dans l'ordre / trier la carte afin que les clés soient dans l'ordre et que les valeurs correspondent?

Voici le code .

gramme.ninja
la source
Possible duplication de Comment parcourir une carte en golang dans l'ordre?
RedGrittyBrick

Réponses:

157

Le blog Go: Go maps en action a une excellente explication.

Lors d'une itération sur une carte avec une boucle de plage, l'ordre d'itération n'est pas spécifié et il n'est pas garanti qu'il soit le même d'une itération à l'autre. Depuis Go 1, le runtime randomise l'ordre d'itération de la carte, car les programmeurs se sont appuyés sur l'ordre d'itération stable de l'implémentation précédente. Si vous avez besoin d'un ordre d'itération stable, vous devez gérer une structure de données distincte qui spécifie cet ordre.

Voici ma version modifiée de l'exemple de code: http://play.golang.org/p/dvqcGPYy3-

package main

import (
    "fmt"
    "sort"
)

func main() {
    // To create a map as input
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    // To store the keys in slice in sorted order
    keys := make([]int, len(m))
    i := 0
    for k := range m {
        keys[i] = k
        i++
    }
    sort.Ints(keys)

    // To perform the opertion you want
    for _, k := range keys {
        fmt.Println("Key:", k, "Value:", m[k])
    }
}

Production:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c
Mingyu
la source
38
Cela peut être amélioré avec keys := make([]int, len(m)), puis insérer par index keys[i] = kau lieu deappend
jpillora
18

Selon la spécification Go , l'ordre d'itération sur une carte n'est pas défini et peut varier entre les exécutions du programme. En pratique, non seulement il n'est pas défini, mais il est en fait volontairement aléatoire. En effet, cela était autrefois prévisible et que les développeurs de langage Go ne voulaient pas que les gens se fient à un comportement non spécifié, ils l'ont donc délibérément aléatoire de sorte qu'il était impossible de se fier à ce comportement.

Ce que vous aurez à faire, alors, est de tirer les clés dans une tranche, de les trier, puis de les placer sur la tranche comme ceci:

var m map[keyType]valueType
keys := sliceOfKeys(m) // you'll have to implement this
for _, k := range keys {
    v := m[k]
    // k is the key and v is the value; do your computation here
}
Joshlf
la source
les tranches d'attente font partie d'un tableau. Comment créer une tranche des clés de la carte uniquement?
gramme.ninja
1
Dans Go, le mot «slice» fait référence à une structure de données qui est essentiellement analogue aux tableaux Java. C'est juste un problème de terminologie. En ce qui concerne l'obtention des clés, vous devez explicitement parcourir la carte et construire une tranche au fur et à mesure comme ceci: play.golang.org/p/ZTni0o19Lr
joshlf
Ah ok. Merci de m'apprendre. Maintenant, il imprime tout même alors tout impair. play.golang.org/p/K2y3m4Zzqd Comment puis-je le faire alterner pour qu'il soit en ordre?
gramme.ninja
1
Vous devrez trier la tranche que vous récupérez (ou, alternativement, la trier dans mapKeys avant de revenir). Vous voudrez vérifier le package de tri .
joshlf
14

Toutes les réponses ici contiennent maintenant l'ancien comportement des cartes. Dans Go 1.12+, vous pouvez simplement imprimer une valeur de carte et elle sera automatiquement triée par clé. Cela a été ajouté car il permet de tester facilement les valeurs de la carte.

func main() {
    m := map[int]int{3: 5, 2: 4, 1: 3}
    fmt.Println(m)

    // In Go 1.12+
    // Output: map[1:3 2:4 3:5]

    // Before Go 1.12 (the order was undefined)
    // map[3:5 2:4 1:3]
}

Les cartes sont désormais imprimées dans l'ordre trié par clé pour faciliter les tests. Les règles de commande sont:

  • Le cas échéant, nul se compare à un faible
  • les entiers, les flottants et les chaînes classés par <
  • NaN compare moins que les flotteurs non NaN
  • bool compare false avant true
  • Complexe compare réel, puis imaginaire
  • Comparaison des pointeurs par adresse de machine
  • Comparaison des valeurs de canal par adresse de machine
  • Les structures comparent chaque champ tour à tour
  • Les tableaux comparent chaque élément tour à tour
  • Les valeurs d'interface se comparent d'abord par Reflect.Type décrivant le type concret, puis par valeur concrète comme décrit dans les règles précédentes.

Lors de l'impression de cartes, les valeurs clés non réflexives telles que NaN étaient précédemment affichées sous la forme <nil>. À partir de cette version, les valeurs correctes sont imprimées.

En savoir plus ici .

Inanc Gumus
la source
8
Cela ne semble s'appliquer qu'au package fmt et à l'impression. La question demande comment trier une carte, pas comment imprimer une carte triée?
Tim
1
Il a partagé un lien de terrain de jeu. Là-dedans, il imprime juste une carte.
Inanc Gumus
2

Si, comme moi, vous trouvez que vous voulez essentiellement le même code de tri à plusieurs endroits, ou que vous voulez simplement réduire la complexité du code, vous pouvez faire abstraction du tri lui-même à une fonction distincte, à laquelle vous passez la fonction qui fait le travail réel que vous souhaitez (qui serait différent à chaque site d'appel, bien sûr).

Étant donné une carte avec le type de clé Ket le type de valeur V, représentés comme <K>et <V>ci - dessous, la fonction de tri commune peut ressembler à ce modèle de code Go (que Go version 1 ne prend pas en charge en l'état):

/* Go apparently doesn't support/allow 'interface{}' as the value (or
/* key) of a map such that any arbitrary type can be substituted at
/* run time, so several of these nearly-identical functions might be
/* needed for different key/value type combinations. */
func sortedMap<K><T>(m map[<K>]<V>, f func(k <K>, v <V>)) {
    var keys []<K>
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)  # or sort.Ints(keys), sort.Sort(...), etc., per <K>
    for _, k := range keys {
        v := m[k]
        f(k, v)
    }
}

Puis appelez-le avec la carte d'entrée et une fonction (en prenant (k <K>, v <V>) comme arguments d'entrée) qui est appelée sur les éléments de la carte dans l'ordre des touches triées.

Ainsi, une version du code dans la réponse publiée par Mingu pourrait ressembler à:

package main

import (
    "fmt"
    "sort"
)

func sortedMapIntString(m map[int]string, f func(k int, v string)) {
    var keys []int
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Ints(keys)
    for _, k := range keys {
        f(k, m[k])
    }
}

func main() {
    // Create a map for processing
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    sortedMapIntString(m,
        func(k int, v string) { fmt.Println("Key:", k, "Value:", v) })
}

La sortedMapIntString()fonction peut être réutilisée pour tout map[int]string(en supposant que le même ordre de tri est souhaité), en gardant chaque utilisation à seulement deux lignes de code.

Les inconvénients comprennent:

  • C'est plus difficile à lire pour les personnes qui ne sont pas habituées à utiliser des fonctions de première classe
  • Cela pourrait être plus lent (je n'ai pas fait de comparaisons de performances)

D'autres langues ont diverses solutions:

  • Si l'utilisation de <K>et <V>(pour désigner les types de la clé et de la valeur) semble un peu familière, ce modèle de code n'est pas très différent des modèles C ++.
  • Clojure et d'autres langages prennent en charge les cartes triées en tant que types de données fondamentaux.
  • Bien que je ne connaisse aucun moyen pour Go de créer rangeun type de première classe tel qu'il puisse être remplacé par une coutume ordered-range(à la place du rangecode d'origine), je pense que certains autres langages fournissent des itérateurs suffisamment puissants pour accomplir la même chose chose.
James Craig Burley
la source
2
Il peut être intéressant de noter, pour les débutants, que la syntaxe <K>, <V> n'est pas prise en charge dans Go.
justinhj
2

En réponse à James Craig Burley réponse . Afin de créer une conception propre et réutilisable, on peut choisir une approche plus orientée objet. De cette façon, les méthodes peuvent être liées en toute sécurité aux types de la carte spécifiée. Pour moi, cette approche me semble plus propre et organisée.

Exemple:

package main

import (
    "fmt"
    "sort"
)

type myIntMap map[int]string

func (m myIntMap) sort() (index []int) {
    for k, _ := range m {
        index = append(index, k)
    }
    sort.Ints(index)
    return
}

func main() {
    m := myIntMap{
        1:  "one",
        11: "eleven",
        3:  "three",
    }
    for _, k := range m.sort() {
        fmt.Println(m[k])
    }
}

Exemple de terrain de jeu étendu avec plusieurs types de cartes.

Note importante

Dans tous les cas, la carte et la tranche triée sont découplées à partir du moment où la forboucle sur la carte rangeest terminée. Cela signifie que si la carte est modifiée après la logique de tri, mais avant de l'utiliser, vous pouvez avoir des problèmes. (Pas de thread / Go routine sûre). S'il y a un changement d'accès en écriture à la carte parallèle, vous devrez utiliser un mutex autour des écritures et de la forboucle triée .

mutex.Lock()
for _, k := range m.sort() {
    fmt.Println(m[k])
}
mutex.Unlock()
Tim
la source
0

Cela vous fournit l'exemple de code sur la carte de tri. En gros, voici ce qu'ils fournissent:

var keys []int
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark1-8      2863149           374 ns/op         152 B/op          5 allocs/op

et c'est ce que je suggérerais d'utiliser à la place :

keys := make([]int, 0, len(myMap))
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark2-8      5320446           230 ns/op          80 B/op          2 allocs/op

Le code complet peut être trouvé dans ce Go Playground .

Erikas
la source