Ottenere una fetta di chiavi da una mappa


230

Esiste un modo più semplice / migliore per ottenere una fetta di chiavi da una mappa in Go?

Attualmente sto ripetendo la mappa e copiando le chiavi in ​​una porzione:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
La risposta corretta è no, non esiste un modo più semplice / migliore.
dldnh

Risposte:


202

Per esempio,

package main

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

Per essere efficiente in Go, è importante ridurre al minimo le allocazioni di memoria.


29
È leggermente meglio impostare la dimensione effettiva anziché la capacità ed evitare del tutto l'aggiunta. Vedi la mia risposta per i dettagli.
Vinay Pai,

3
Nota che se mymapnon è una variabile locale (ed è quindi soggetta a crescita / riduzione), questa è l'unica soluzione corretta - assicura che se la dimensione dei mymapcambiamenti tra l'inizializzazione di keyse il forciclo, non ci sarà alcun out- problemi di limiti.
Melllvar,

12
le mappe non sono sicure in caso di accesso simultaneo, nessuna soluzione è accettabile se un'altra goroutine potrebbe cambiare la mappa.
Vinay Pai,

@VinayPai va bene leggere da una mappa da più goroutine ma non scrivere
darethas

@daretha questo è un malinteso comune. Il rilevatore di gara segnalerà questo utilizzo dall'1.6. Dalle note di rilascio: "Come sempre, se un goroutine sta scrivendo su una mappa, nessun altro goroutine dovrebbe leggere o scrivere la mappa contemporaneamente. Se il runtime rileva questa condizione, stampa una diagnosi e blocca il programma." golang.org/doc/go1.6#runtime
Vinay Pai

375

Questa è una vecchia domanda, ma ecco i miei due centesimi. La risposta di PeterSO è leggermente più concisa, ma leggermente meno efficiente. Sai già quanto sarà grande, quindi non hai nemmeno bisogno di usare append:

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

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

Nella maggior parte delle situazioni probabilmente non farà molta differenza, ma non è molto più lavoro e nei miei test (usando una mappa con 1.000.000 di casuali int64 chiavi e quindi generare l'array di chiavi dieci volte con ogni metodo), si trattava di 20% più veloce nell'assegnare i membri dell'array direttamente rispetto all'uso di append.

Sebbene l'impostazione della capacità elimini le riallocazioni, l'appendice deve ancora fare un lavoro extra per verificare se hai raggiunto la capacità su ciascuna appendice.


46
Sembra esattamente lo stesso del codice OP. Sono d'accordo che questo è il modo migliore, ma sono curioso di sapere se ho perso la differenza tra il codice di questa risposta e il codice di OP.
Emmaly Wilson

4
Bene, in qualche modo ho guardato le altre risposte e ho perso che la mia risposta è esattamente la stessa dell'OP. Vabbè, almeno ora sappiamo approssimativamente quale sia la penalità per l'uso di append inutilmente :)
Vinay Pai

5
Perché non si utilizza un indice con la gamma, for i, k := range mymap{. In questo modo non hai bisogno di i ++?
mvndaai,

30
Forse mi manca qualcosa qui, ma se lo hai fatto i, k := range mymap, allora isaranno le chiavi e ksaranno i valori corrispondenti a quelle chiavi nella mappa. Questo non ti aiuterà a popolare una fetta di chiavi.
Vinay Pai,

4
@Alaska se ti preoccupi del costo di allocazione di una variabile contatore temporanea, ma pensi che una chiamata di funzione occuperà meno memoria, dovresti informarti su ciò che accade effettivamente quando viene chiamata una funzione. Suggerimento: non è un incantesimo magico che fa le cose gratuitamente. Se ritieni che la risposta attualmente accettata sia sicura sotto l'accesso simultaneo, devi anche tornare alle basi: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai,

79

Puoi anche prendere un array di chiavi con type []Valueper metodo MapKeysdi struct Valuedal pacchetto "reflection":

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

1
Penso che sia un buon approccio se esiste la possibilità di un accesso simultaneo alla mappa: questo non andrà in panico se la mappa cresce durante il ciclo. Per quanto riguarda le prestazioni, non ne sono davvero sicuro, ma sospetto che superi la soluzione di aggiunta.
Atila Romero,

@AtilaRomero Non sono sicuro che questa soluzione abbia qualche vantaggio, ma quando si usa la riflessione per qualsiasi scopo, questo è più utile, perché consente di prendere una chiave come Valore digitato direttamente.
Denis Kreshikhin,

10
C'è un modo per convertirlo in []string?
Doron Behar,

14

Un modo migliore per farlo sarebbe usare append:

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

A parte questo, sei sfortunato: Go non è un linguaggio molto espressivo.


10
Tuttavia, è meno efficiente dell'originale: append farà più allocazioni per far crescere l'array sottostante e deve aggiornare la lunghezza della sezione ogni chiamata. Dettokeys = make([]int, 0, len(mymap)) che si sbarazzerà delle allocazioni, ma mi aspetto che sarà ancora più lento.
Nick Craig-Wood,

1
Questa risposta è più sicura dell'uso di len (mymap), se qualcun altro cambia la mappa mentre viene eseguita la copia.
Atila Romero,

7

Ho fatto un benchmark impreciso sui tre metodi descritti in altre risposte.

Ovviamente pre-allocare la fetta prima di tirare i tasti è più veloce di appending, ma sorprendentemente, il reflect.ValueOf(m).MapKeys()metodo è significativamente più lento di quest'ultimo:

 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

Ecco il codice: https://play.golang.org/p/Z8O6a2jyfTH (eseguendolo nel parco giochi si interrompe sostenendo che ci vuole troppo tempo, quindi, bene, eseguirlo localmente.)


2
Nella tua keysAppendfunzione, puoi impostare la capacità keysdell'array con make([]uint64, 0, len(m)), che ha cambiato drasticamente le prestazioni di quella funzione per me.
keithbhunter

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)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.