En golang, ¿hay una buena manera de obtener una porción de valores de un mapa?

89

Si tengo un mapa m, ¿hay una mejor manera de obtener una porción de los valores v entonces?

package main
import (
  "fmt"
)

func main() {
    m := make(map[int]string)

    m[1] = "a"
    m[2] = "b"
    m[3] = "c"
    m[4] = "d"

    // Can this be done better?
    v := make([]string, len(m), len(m))
    idx := 0
    for  _, value := range m {
       v[idx] = value
       idx++
    }

    fmt.Println(v)
 }

¿Existe una función incorporada en un mapa? ¿Hay una función en un paquete Go, o este es el mejor código para hacer si tengo que hacerlo?

masebase
fuente
1
en lugar de '_' en su bucle for, llámelo idx y abandone el negocio idx ++
Peter Agnew
No, no puede, cuando recorre un mapa devuelve clave, valor, no índice, valor. En su ejemplo, usa 1 como la primera clave y eso hará que los índices en el segmento v sean incorrectos porque el índice de inicio será 1 y no cero, y cuando llegue a 4 estará fuera de rango. play.golang.org/p/X8_SbgxK4VX
Popmedic
@Popmedic En realidad, sí que puede. basta con sustituir _con idxy utilizar idx-1la hora de asignar los valores de división.
hewiefreeman
3
@ newplayer66, ese es un patrón muy peligroso.
Popmedic

Respuestas:

60

Lamentablemente no. No hay una forma incorporada de hacer esto.

Como nota al margen, puede omitir el argumento de capacidad en la creación de su segmento:

v := make([]string, len(m))

Se da a entender que la capacidad es la misma que la longitud aquí.

Jimt
fuente
Creo que hay una mejor manera: stackoverflow.com/a/61953291/1162217
Lukas Lukac
58

Como una adición a la publicación de Jimt:

También puede usar en appendlugar de asignar explícitamente los valores a sus índices:

m := make(map[int]string)

m[1] = "a"
m[2] = "b"
m[3] = "c"
m[4] = "d"

v := make([]string, 0, len(m))

for  _, value := range m {
   v = append(v, value)
}

Tenga en cuenta que la longitud es cero (todavía no hay elementos presentes) pero la capacidad (espacio asignado) se inicializa con el número de elementos de m. Esto se hace para appendque no sea necesario asignar memoria cada vez que se vagote la capacidad del segmento .

También puede makecortar el segmento sin el valor de capacidad y dejar appendasignar la memoria por sí mismo.

nemo
fuente
Me preguntaba si esto sería más lento (asumiendo la asignación por adelantado). Hice una prueba de referencia burda con un mapa [int] int y parecía un 1-2% más lento. ¿Alguna idea de si esto es algo de qué preocuparse o simplemente seguirlo?
masebase
1
Asumiría que append es un poco más lento, pero esa diferencia es, en la mayoría de los casos, insignificante. Benchmark que compara la asignación directa y el anexo .
nemo
1
Tenga cuidado de mezclar esto con la respuesta anterior: agregar a la matriz después de usarlo se make([]appsv1.Deployment, len(d))agregará a un grupo de elementos vacíos que se crearon cuando asignó len(d)elementos vacíos.
Anirudh Ramanathan
1

Hasta donde yo sé, go no tiene un método de concatenación de cadenas / bytes en una cadena resultante sin hacer al menos / dos / copias.

Actualmente tiene que aumentar un [] byte ya que todos los valores de cadena son constantes, LUEGO debe usar la cadena incorporada para que el lenguaje cree un objeto de cadena 'bendecido', que copiará el búfer para ya que algo en algún lugar podría tener una referencia a la dirección que respalda el byte [].

Si un byte [] es adecuado, entonces puede obtener una ventaja muy leve sobre los bytes. Únase a la función haciendo una asignación y haciendo las llamadas de copia usted mismo.

package main
import (
  "fmt"
)

func main() {
m := make(map[int]string)

m[1] = "a" ;    m[2] = "b" ;     m[3] = "c" ;    m[4] = "d"

ip := 0

/* If the elements of m are not all of fixed length you must use a method like this;
 * in that case also consider:
 * bytes.Join() and/or
 * strings.Join()
 * They are likely preferable for maintainability over small performance change.

for _, v := range m {
    ip += len(v)
}
*/

ip = len(m) * 1 // length of elements in m
r := make([]byte, ip, ip)
ip = 0
for  _, v := range m {
   ip += copy(r[ip:], v)
}

// r (return value) is currently a []byte, it mostly differs from 'string'
// in that it can be grown and has a different default fmt method.

fmt.Printf("%s\n", r)
}
Michael J. Evans
fuente
1

Puede utilizar este mapspaquete:

go get https://github.com/drgrib/maps

Entonces todo lo que tienes que llamar es

values := maps.GetValuesIntString(m)

Es de tipo seguro para esa mapcombinación común . Puede utilizar generateotras funciones de tipo seguro para cualquier otro tipo de mapuso de la mapperherramienta en el mismo paquete.

Divulgación completa: soy el creador de este paquete. Lo creé porque me encontré reescribiendo estas funciones maprepetidamente.

Chris Redford
fuente
1

No necesariamente mejor, pero la forma más limpia de hacer esto es definiendo tanto la LONGITUD y la CAPACIDAD del corte comotxs := make([]Tx, 0, len(txMap))

    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))

    for _, tx := range txMap {
        txs = append(txs, tx)
    }

Ejemplo completo:

package main

import (
    "github.com/davecgh/go-spew/spew"
)

type Tx struct {
    from  string
    to    string
    value uint64
}

func main() {
    // Extra touch pre-defining the Map length to avoid reallocation
    txMap := make(map[string]Tx, 3)
    txMap["tx1"] = Tx{"andrej", "babayaga", 10}
    txMap["tx2"] = Tx{"andrej", "babayaga", 20}
    txMap["tx3"] = Tx{"andrej", "babayaga", 30}

    txSlice := getTXsAsSlice(txMap)
    spew.Dump(txSlice)
}

func getTXsAsSlice(txMap map[string]Tx) []Tx {
    // Defines the Slice capacity to match the Map elements count
    txs := make([]Tx, 0, len(txMap))
    for _, tx := range txMap {
        txs = append(txs, tx)
    }

    return txs
}

Solución simple pero con muchas trampas. Lea esta publicación de blog para obtener más detalles: https://web3.coach/golang-how-to-convert-map-to-slice-three-gotchas

Lukas Lukac
fuente
La respuesta no es "incorrecta". La pregunta utiliza un índice y no un anexo para el segmento. Si usa agregar en el corte, entonces sí, establecer la longitud al frente sería malo. Aquí está la pregunta, ya que es play.golang.org/p/nsIlIl24Irn De acuerdo, esa pregunta no es idiomática, vaya y todavía estaba aprendiendo. Una versión ligeramente mejorada más parecida a la que está hablando es play.golang.org/p/4SKxC48wg2b
masebase
1
Hola @masebase, lo considero "incorrecto" porque dice: "Desafortunadamente, no. No hay una forma incorporada de hacer esto", pero hay una solución "mejor" que ambos señalamos ahora 2 años después: usando el anexo () y definiendo tanto la longitud como la capacidad. Pero veo su punto de que llamarlo "incorrecto" tampoco es exacto. Cambiaré mi primera oración a: "No necesariamente mejor, pero la forma más limpia de hacer esto es". He hablado con ~ 10 desarrolladores y todos estuvieron de acuerdo en que append () es una forma más limpia de convertir un mapa en un segmento sin usar un índice auxiliar. Aprendí esto también en la forma de publicar
Lukas Lukac