Obtener una porción de llaves de un mapa

230

¿Hay alguna forma más simple / mejor de obtener una porción de claves de un mapa en Go?

Actualmente estoy iterando sobre el mapa y copiando las claves en un segmento:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Saswat Padhi
fuente
19
La respuesta correcta es no, no hay una manera más simple / más agradable.
dldnh

Respuestas:

203

Por ejemplo,

package main

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

Para ser eficiente en Go, es importante minimizar las asignaciones de memoria.

PeterSO
fuente
29
Es un poco mejor establecer el tamaño real en lugar de la capacidad y evitar agregarlo por completo. Vea mi respuesta para más detalles.
Vinay Pai
3
Tenga en cuenta que si mymapno es una variable local (y, por lo tanto, está sujeta a crecimiento / reducción), esta es la única solución adecuada: garantiza que si el tamaño de los mymapcambios entre la inicialización de keysy el forbucle, no habrá ninguna salida. problemas de límites.
Melllvar
12
los mapas no son seguros con acceso concurrente, ninguna de las soluciones es aceptable si otra rutina puede cambiar el mapa.
Vinay Pai
@VinayPai está bien leer desde un mapa de múltiples goroutines pero no escribir
darethas
@darethas es un error común. El detector de carrera marcará este uso desde 1.6. De las notas de la versión: "Como siempre, si una gorutina está escribiendo en un mapa, ninguna otra gorita debería leer o escribir el mapa simultáneamente. Si el tiempo de ejecución detecta esta condición, imprime un diagnóstico y bloquea el programa". golang.org/doc/go1.6#runtime
Vinay Pai
374

Esta es una vieja pregunta, pero aquí están mis dos centavos. La respuesta de PeterSO es un poco más concisa, pero un poco menos eficiente. Ya sabes lo grande que será, así que ni siquiera necesitas usar append:

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

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

En la mayoría de las situaciones, probablemente no hará mucha diferencia, pero no es mucho más trabajo, y en mis pruebas (usando un mapa con 1,000,000 int64claves aleatorias y luego generando la matriz de claves diez veces con cada método), se trataba de 20% más rápido para asignar miembros de la matriz directamente que para usar append.

Si bien establecer la capacidad elimina las reasignaciones, append todavía tiene que hacer un trabajo adicional para verificar si ha alcanzado la capacidad en cada anexo.

Vinay Pai
fuente
46
Esto se ve exactamente igual que el código del OP. Estoy de acuerdo en que esta es la mejor manera, pero tengo curiosidad si me he perdido la diferencia entre el código de esta respuesta y el código de OP.
Emmaly Wilson
44
Buen punto, de alguna manera miré las otras respuestas y perdí que mi respuesta es exactamente la misma que la OP. Oh, bueno, al menos ahora sabemos aproximadamente cuál es la penalidad por usar agregar innecesariamente :)
Vinay Pai
55
¿Por qué no estás usando un índice con el rango for i, k := range mymap{? De esa manera no necesitas el i ++?
mvndaai
30
Tal vez me estoy perdiendo algo aquí, pero si lo hiciste i, k := range mymap, entonces iserán claves y kserán valores correspondientes a esas claves en el mapa. En realidad, eso no te ayudará a llenar una porción de claves.
Vinay Pai
44
@Alaska si le preocupa el costo de asignar una variable de contador temporal, pero cree que una llamada a función requerirá menos memoria, debe informarse sobre lo que realmente sucede cuando se llama a una función. Sugerencia: no es un encantamiento mágico que hace cosas gratis. Si cree que la respuesta actualmente aceptada es segura con acceso concurrente, también debe volver a lo básico: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai
79

También puede tomar una matriz de claves con tipo []Valuepor método MapKeysde estructura Valuedel paquete "reflect":

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
fuente
1
Creo que este es un buen enfoque si existe la posibilidad de un acceso simultáneo al mapa: esto no entrará en pánico si el mapa crece durante el ciclo. Sobre el rendimiento, no estoy realmente seguro, pero sospecho que supera la solución de agregar.
Atila Romero
@AtilaRomero No estoy seguro de que esta solución tenga alguna ventaja, pero cuando se usa la reflexión para cualquier propósito, esto es más útil, ya que permite tomar una clave como Valor escrito directamente.
Denis Kreshikhin
10
¿Hay alguna manera de convertir esto []string?
Doron Behar
14

Una mejor manera de hacer esto sería usar append:

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

Aparte de eso, no tienes suerte: Go no es un lenguaje muy expresivo.

pliegue derecho
fuente
10
Sin embargo, es menos eficiente que el original: append realizará múltiples asignaciones para hacer crecer la matriz subyacente, y tiene que actualizar la longitud del segmento en cada llamada. Diciendo keys = make([]int, 0, len(mymap))se eliminarán las asignaciones, pero espero que aún sea más lento.
Nick Craig-Wood
1
Esta respuesta es más segura que usar len (mymap), si alguien más cambia el mapa mientras se realiza la copia.
Atila Romero
7

Hice un punto de referencia incompleto sobre los tres métodos descritos en otras respuestas.

Obviamente, preasignar el segmento antes de tirar de las teclas es más rápido que appending, pero sorprendentemente, el reflect.ValueOf(m).MapKeys()método es significativamente más lento que el último:

 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

Aquí está el código: https://play.golang.org/p/Z8O6a2jyfTH (ejecutarlo en el patio de juegos aborta alegando que lleva demasiado tiempo, así que, bueno, ejecútelo localmente).

Nico Villanueva
fuente
2
En su keysAppendfunción, puede establecer la capacidad de la keysmatriz con make([]uint64, 0, len(m)), lo que cambió drásticamente el rendimiento de esa función para mí.
keithbhunter 01 de
1

Visita 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
fuente