Is there any simpler/nicer way of getting a slice of keys from a map in Go?
Currently I am iterating over the map and copying the keys to a slice:
i := 0
keys := make([]int, len(mymap))
for k := range mymap {
keys[i] = k
i++
}
This is an old question, but here's my two cents. PeterSO's answer is slightly more concise, but slightly less efficient. You already know how big it's going to be so you don't even need to use append:
keys := make([]int, len(mymap))
i := 0
for k := range mymap {
keys[i] = k
i++
}
In most situations it probably won't make much of a difference, but it's not much more work, and in my tests (using a map with 1,000,000 random int64 keys and then generating the array of keys ten times with each method), it was about 20% faster to assign members of the array directly than to use append.
Although setting the capacity eliminates reallocations, append still has to do extra work to check if you've reached capacity on each append.
for i, k := range mymap{. That way you don't need the i++?i, k := range mymap, then i will be keys and k will be values corresponding to those keys in the map. That won't actually help you populate a slice of keys.For example,
package main
func main() {
mymap := make(map[int]string)
keys := make([]int, 0, len(mymap))
for k := range mymap {
keys = append(keys, k)
}
}
To be efficient in Go, it's important to minimize memory allocations.
mymap is not a local variable (and is therefore subject to growing/shrinking), this is the only proper solution - it ensures that if the size of mymap changes between the initialization of keys and the for loop, there won't be any out-of-bounds issues.The function maps.Keys will be moved into the standard library, however as you can see from the method definition, it doesn't just return a slice of keys: it returns an iterator. Therefore:
The iterator is a function that you can directly use in a range clause:
import (
"maps"
)
func main() {
m := map[string]int{"alpha": 1, "bravo": 2}
for k := range maps.Keys(m) {
// ...
}
}
Playground: https://go.dev/play/p/r_cm9KiTqqF?v=gotip
If you need the map keys into a slice, you have to collect them from the iterator using slices.Collect:
import (
"fmt"
"maps"
"slices"
)
func main() {
m := map[string]int{"alpha": 1, "bravo": 2}
keys := slices.Collect(maps.Keys(m))
fmt.Println(keys) // [alpha bravo]
}
Playground: https://go.dev/play/p/GSWz3xqnI-9?v=gotip
This is efficient because the iterator is a function that yields values on demand. Though if you find this cumbersome, you can still write your own generic function as the old golang.org/x/exp/maps.Keys did (sample code below).
You can get the keys of any map with golang.org/x/exp/maps.Keys.
Example usage:
intMap := map[int]int{1: 1, 2: 2}
intKeys := maps.Keys(intMap)
// intKeys is []int
fmt.Println(intKeys)
strMap := map[string]int{"alpha": 1, "bravo": 2}
strKeys := maps.Keys(strMap)
// strKeys is []string
fmt.Println(strKeys)
maps package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19 the future.
Playground: https://go.dev/play/p/fkm9PrJYTly
For those who don't like to import exp packages, here's the source code (originally authored by Ian Lance Taylor), which as you can see is very simple:
// Keys returns the keys of the map m.
// The keys will be an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
NOTE: In Go 1.21 a part of the maps package has been moved into the standard library, but not maps.Keys. For details see the Go issue maps: remove Keys and Values for Go 1.21 tl;dr the Keys method might end up having a different signature. So in Go 1.21 the solutions presented here (use x/exp/maps or copy the source) are still applicable.
~ in ~map[K]V?for k := range maps.Keys(m) and for k := range mfor k := range m is less surprising. maps.Keys is useful if you need to work with iterators, e.g. you can pass it to slices.Collect or other similar functions that accept iter.Seq[alpha bravo] or [bravo alpha].You also can take an array of keys with type []Value by method MapKeys of struct Value from package "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]
}
[]string?I made a sketchy benchmark on the three methods described in other responses.
Obviously pre-allocating the slice before pulling the keys is faster than appending, but surprisingly, the reflect.ValueOf(m).MapKeys() method is significantly slower than the latter:
❯ 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
Here's the code: https://play.golang.org/p/Z8O6a2jyfTH (running it in the playground aborts claiming that it takes too long, so, well, run it locally.)
keysAppend function, you can set the capacity of the keys array with make([]uint64, 0, len(m)), which drastically changed the performance of that function for me.A nicer way to do this would be to use append:
keys = []int{}
for k := range mymap {
keys = append(keys, k)
}
Other than that, you’re out of luck—Go isn’t a very expressive language.
keys = make([]int, 0, len(mymap)) will get rid of the allocations but I expect it will still be slower.Assuming map is of type map[int]string, you could get keys and values using the experimental maps package from the standard library:
package main
import (
"fmt"
"golang.org/x/exp/maps"
)
func main() {
mymap := map[int]string{1: "foo", 2: "bar", 3: "biz"}
fmt.Println(maps.Keys(mymap))
fmt.Println(maps.Values(mymap))
}
Output:
[2 3 1]
[bar biz foo]
A generic version (go 1.18+) of Vinay Pai's answer.
// MapKeysToSlice extract keys of map as slice,
func MapKeysToSlice[K comparable, V any](m map[K]V) []K {
keys := make([]K, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
return keys
}
Visit 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)
}
There is a cool lib called lo
A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)
With this lib you could do many convinient operations like map, filter, reduce and more. Also there are some helpers for map type
Creates an array of the map keys.
keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2})
// []string{"bar", "foo"}
Creates an array of the map values.
values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
slices.Collect(maps.Keys(someMap)), to get slice in go 1.23+