¿Cómo ordenar un Map [string] int por sus valores?

81

Dado este bloque de código

map[string]int {"hello":10, "foo":20, "bar":20}

Me gustaria imprimir

foo, 20
bar, 20
hello, 10

En el orden de mayor a menor

¡Gracias!

samol
fuente

Respuestas:

92

Encontré la respuesta en Golang-nuts por Andrew Gerrand

Puede implementar la interfaz de clasificación escribiendo las funciones len / less / swap

func rankByWordCount(wordFrequencies map[string]int) PairList{
  pl := make(PairList, len(wordFrequencies))
  i := 0
  for k, v := range wordFrequencies {
    pl[i] = Pair{k, v}
    i++
  }
  sort.Sort(sort.Reverse(pl))
  return pl
}

type Pair struct {
  Key string
  Value int
}

type PairList []Pair

func (p PairList) Len() int { return len(p) }
func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
func (p PairList) Swap(i, j int){ p[i], p[j] = p[j], p[i] }

Para ver la publicación original, encuéntrela aquí https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw

samol
fuente
1
... excepto que Lessestá devolviendo el resultado incorrecto. Para ordenar al revés, use >.
Fred Foo
3
@larsmans ¡Mi mal! Gracias por mencionarlo. En su lugar, he usado sort.Reverse para obtener los resultados inversos
samol
2
Aún mejor, ni siquiera lo sabía sort.Reverse. +1.
Fred Foo
72

Hay una nueva función sort.Slice en go 1.8, así que ahora esto es más simple.

package main

import (
    "fmt"
    "sort"
)

func main() {
    m := map[string]int{
        "something": 10,
        "yo":        20,
        "blah":      20,
    }

    type kv struct {
        Key   string
        Value int
    }

    var ss []kv
    for k, v := range m {
        ss = append(ss, kv{k, v})
    }

    sort.Slice(ss, func(i, j int) bool {
        return ss[i].Value > ss[j].Value
    })

    for _, kv := range ss {
        fmt.Printf("%s, %d\n", kv.Key, kv.Value)
    }
}

https://play.golang.org/p/y1_WBENH4N

voutasaurus
fuente
No me gusta cómo la salida no es el mapa con el que comencé
hendry
@hendry esta respuesta es específicamente en respuesta al formato de la pregunta original. En go1.12, puede imprimir el mapa y se ordenará, consulte el problema: github.com/golang/go/issues/21095
voutasaurus
casi perfecto, sugiero manejar elementos con el mismo valor.
Tommaso Barbugli
@TommasoBarbugli ¿cómo? ¿Hacerlos estables? ¿O ordenados alfabéticamente? Es bastante seguro que la estabilidad es imposible porque Go aleatoriza el orden de iteración del mapa específicamente para evitar que dependa de un orden que se indica explícitamente como no significativo para el compilador. Por orden alfabético, puede modificar fácilmente la función anónima pasada a sort.Slice
voutasaurus
1
También hay un sort.SliceStable (también agregado en Go 1.8) que conserva el orden original de los elementos iguales.
Dave Yarwood
16

Por ejemplo:

package main

import (
        "fmt"
        "sort"
)

func main() {
        m := map[string]int{"hello": 10, "foo": 20, "bar": 20}
        n := map[int][]string{}
        var a []int
        for k, v := range m {
                n[v] = append(n[v], k)
        }
        for k := range n {
                a = append(a, k)
        }
        sort.Sort(sort.Reverse(sort.IntSlice(a)))
        for _, k := range a {
                for _, s := range n[k] {
                        fmt.Printf("%s, %d\n", s, k)
                }
        }
}

Patio de recreo


Salida:

foo, 20
bar, 20
hello, 10
zzzz
fuente
@DarshanComputing: Gracias, arreglado.
zzzz
1
Esto supone que los valores no tienen identidades.
newacct
2
@newacct: Resuelve solo el problema de OP, no el caso general ;-)
zzzz
esta solución es la que funcionó para mi caso también, simple de entender.
Tommy
1

A menudo necesito ordenar map[string]intalgo que estoy contando y he estado usando lo siguiente.

func rankMapStringInt(values map[string]int) []string {
    type kv struct {
        Key   string
        Value int
    }
    var ss []kv
    for k, v := range values {
        ss = append(ss, kv{k, v})
    }
    sort.Slice(ss, func(i, j int) bool {
        return ss[i].Value > ss[j].Value
    })
    ranked := make([]string, len(values))
    for i, kv := range ss {
        ranked[i] = kv.Key
    }
    return ranked
}

Úselo para iterar sobre las claves en orden de valor

values := map[string]int{"foo": 10, "bar": 20, "baz": 1}

for i, index := range rankMapStringInt(values) {
    fmt.Printf("%3d: %s -> %d", i, index, values[index])
}
Xeoncross
fuente
0

En mi caso, estaba tratando con un programa que creé. En este programa, creé un mapa como tú, con stringy int. Luego descubrí, como tú, que Go no tiene una forma incorporada de ordenar algo como esto. Leí las otras respuestas y realmente no me gustó lo que leí.

Así que intenté pensar en el problema de otra manera. Go puede usar sort.Ints con una rebanada. Además, Go puede usar sort.Slice con un comparador personalizado. Entonces, en lugar de crear un mapa de stringy int, creé un mapa structde stringy int. Entonces puedes ordenar:

package main

import (
   "fmt"
   "sort"
)

type File struct {
   Name string
   Size int
}

func main() {
   a := []File{{"april.txt", 9}, {"may.txt", 7}}
   f := func (n, n1 int) bool {
      return a[n].Size < a[n1].Size
   }
   sort.Slice(a, f)
   fmt.Println(a)
}

Esto no funcionará para todos, porque tal vez se vea obligado a lidiar con un mapa creado por otra persona. Pero fue útil para mí. La buena parte es que, a diferencia de todas las otras respuestas, esta no usa bucles.

Steven Penny
fuente
-1

Ordene las claves primero por valor y luego itere el mapa:

package main

import (
    "fmt"
    "sort"
)

func main() {
    counts := map[string]int{"hello": 10, "foo": 20, "bar": 20}

    keys := make([]string, 0, len(counts))
    for key := range counts {
        keys = append(keys, key)
    }
    sort.Slice(keys, func(i, j int) bool { return counts[keys[i]] > counts[keys[j]] })

    for _, key := range keys {
        fmt.Printf("%s, %d\n", key, counts[key])
    }
}
AlexanderYastrebov
fuente
1
Estoy seguro de quién está votando negativamente, probablemente aquellos que no leen con atención la función del comparador. También la respuesta aceptada no produce la impresión OP solicitada, sino que introduce una nueva estructura de datos que debe mantenerse en código real. Aquí está el enlace del patio de juegos para mi respuesta play.golang.org/p/Y4lrEm2-hT5
AlexanderYastrebov