Ordenar los valores del mapa de Go por claves

94

Al iterar a través del mapa devuelto en el código, devuelto por la función de tema, las claves no aparecen en orden.

¿Cómo puedo hacer que las claves estén en orden / ordenar el mapa para que las claves estén en orden y los valores correspondan?

Aquí está el código .

gramme.ninja
fuente
Posible duplicado de ¿Cómo recorrer un mapa en Golang en orden?
RedGrittyBrick

Respuestas:

157

El blog Go: Go Maps in action tiene una excelente explicación.

Cuando se itera sobre un mapa con un bucle de rango, el orden de iteración no se especifica y no se garantiza que sea el mismo de una iteración a la siguiente. Desde Go 1, el tiempo de ejecución aleatoriza el orden de iteración del mapa, ya que los programadores confiaban en el orden de iteración estable de la implementación anterior. Si necesita un orden de iteración estable, debe mantener una estructura de datos separada que especifique ese orden.

Aquí está mi versión modificada del código de ejemplo: 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])
    }
}

Salida:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c
Mingyu
fuente
38
Esto se puede mejorar con keys := make([]int, len(m))y luego insertar por índice en keys[i] = klugar deappend
jpillora
18

De acuerdo con la especificación Go , el orden de iteración sobre un mapa no está definido y puede variar entre las ejecuciones del programa. En la práctica, no solo no está definido, sino que en realidad está intencionalmente aleatorizado. Esto se debe a que solía ser predecible, y los desarrolladores del lenguaje Go no querían que las personas confiaran en un comportamiento no especificado, por lo que lo aleatorizaron intencionalmente para que confiar en este comportamiento fuera imposible.

Lo que tendrá que hacer, entonces, es colocar las claves en un segmento, ordenarlas y luego recorrer el segmento de esta manera:

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
fuente
los segmentos de espera son partes de una matriz. ¿Cómo hago una porción de solo las claves en el mapa?
gramme.ninja
1
En Go, la palabra "segmento" se refiere a una estructura de datos que es esencialmente análoga a las matrices de Java. Es solo una cuestión de terminología. En cuanto a obtener las claves, debe recorrer explícitamente el mapa y construir un segmento a medida que avanza de
joshlf
Ah bien. Gracias por enseñarme. Ahora, está imprimiendo todo par y luego todo impar. play.golang.org/p/K2y3m4Zzqd ¿Cómo puedo hacer que se alterne para que esté en orden?
gramme.ninja
1
Deberá ordenar el segmento que obtenga (o, alternativamente, ordenarlo en mapKeys antes de regresar). Querrá consultar el paquete de clasificación .
joshlf
14

Todas las respuestas aquí ahora contienen el antiguo comportamiento de los mapas. En Go 1.12+, puede imprimir un valor de mapa y se ordenará por clave automáticamente. Esto se ha agregado porque permite probar fácilmente los valores del mapa.

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

Los mapas ahora se imprimen en orden de clave para facilitar las pruebas. Las reglas para ordenar son:

  • Cuando corresponde, cero compara bajo
  • ints, floats y strings ordenados por <
  • NaN compara menos de flotadores que no son NaN
  • bool compara falso antes que verdadero
  • Complejo compara real, luego imaginario
  • Los punteros se comparan por dirección de máquina
  • Los valores de canal se comparan por dirección de máquina
  • Las estructuras comparan cada campo por turno
  • Las matrices comparan cada elemento a su vez
  • Los valores de interfaz se comparan primero por reflect.Type que describe el tipo concreto y luego por valor concreto como se describe en las reglas anteriores.

Al imprimir mapas, los valores clave no reflexivos como NaN se mostraban anteriormente como <nil>. A partir de esta versión, se imprimen los valores correctos.

Leer más aquí .

Inanc Gumus
fuente
8
Esto solo parece aplicarse al paquete fmt y a la impresión. La pregunta es cómo ordenar un mapa, no cómo imprimir un mapa ordenado.
Tim
1
Compartió un enlace de patio de recreo. Allí solo imprime un mapa.
Inanc Gumus
2

Si, como yo, descubre que desea esencialmente el mismo código de clasificación en más de un lugar, o simplemente desea mantener baja la complejidad del código, puede abstraer la clasificación en una función separada, a la que le pasa la función que hace el trabajo real que desea (que sería diferente en cada sitio de llamada, por supuesto).

Dado un mapa con el tipo de clave Ky el tipo de valor V, representado como <K>y a <V>continuación, la función de clasificación común podría parecerse a esta plantilla de código Go (que la versión 1 de Go no admite tal como está):

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

Luego llámelo con el mapa de entrada y una función (tomando (k <K>, v <V>)como argumentos de entrada) que se invoca sobre los elementos del mapa en orden de clave ordenada.

Entonces, una versión del código en la respuesta publicada por Mingu podría verse así:

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()función se puede reutilizar para cualquier map[int]string(suponiendo que se desee el mismo orden de clasificación), manteniendo cada uso en solo dos líneas de código.

Las desventajas incluyen:

  • Es más difícil de leer para las personas que no están acostumbradas a usar funciones como de primera clase.
  • Puede que sea más lento (no he hecho comparaciones de rendimiento)

Otros idiomas tienen varias soluciones:

  • Si el uso de <K>y <V>(para denotar tipos para la clave y el valor) parece un poco familiar, esa plantilla de código no es muy diferente a las plantillas de C ++.
  • Clojure y otros lenguajes admiten mapas ordenados como tipos de datos fundamentales.
  • Si bien no conozco ninguna forma en que Go haga rangeun tipo de primera clase que pueda ser sustituido por uno personalizado ordered-range(en lugar del rangecódigo original), creo que algunos otros lenguajes proporcionan iteradores que son lo suficientemente potentes para lograr lo mismo cosa.
James Craig Burley
fuente
2
Vale la pena señalar, para principiantes, que la sintaxis <K>, <V> no es compatible con Go.
justinhj
2

En respuesta a James Craig Burley respuesta . Para hacer un diseño limpio y reutilizable, se puede optar por un enfoque más orientado a objetos. De esta forma, los métodos se pueden vincular de forma segura a los tipos del mapa especificado. Para mí, este enfoque se siente más limpio y organizado.

Ejemplo:

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

Ejemplo de área de juegos extendida con múltiples tipos de mapas.

Nota IMPORTANTE

En todos los casos, el mapa y el segmento ordenado se desacoplan desde el momento en que forfinaliza el bucle sobre el mapa range. Lo que significa que, si el mapa se modifica después de la lógica de clasificación, pero antes de usarlo, puede tener problemas. (No hilo / Ir a rutina segura). Si hay un cambio en el acceso de escritura del mapa paralelo, necesitará usar un mutex alrededor de las escrituras y el forciclo ordenado .

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

Esto le proporciona el ejemplo de código sobre el mapa de clasificación. Básicamente esto es lo que proporcionan:

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

y esto es lo que sugeriría usar en su lugar :

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

El código completo se puede encontrar en este Go Playground .

Erikas
fuente