410

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++
}
blackgreen
  • 34,072
  • 23
  • 111
  • 129
Saswat Padhi
  • 6,044
  • 4
  • 20
  • 25

10 Answers10

550

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.

Freedom_Ben
  • 11,247
  • 10
  • 69
  • 89
Vinay Pai
  • 7,432
  • 1
  • 17
  • 28
  • 78
    This looks exactly the same as the OP's code. I agree that this is the better way, but I'm curious if I've missed the difference between this answer's code and OP's code. – Emmaly Mar 08 '15 at 07:32
  • 8
    Good point, I somehow looked at the other answers and missed that my answer is exactly the same as the OP. Oh well, at least we now know approximately what the penalty is for using append unnecessarily :) – Vinay Pai Mar 10 '15 at 14:43
  • 7
    Why aren't you using an index with the range, `for i, k := range mymap{`. That way you don't need the i++? – mvndaai Apr 29 '16 at 17:57
  • 51
    Maybe I'm missing something here, but if you did `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. – Vinay Pai May 02 '16 at 19:31
  • Note that this solution is not recommended if `mymap` is subject to frequent changes - see my comment on the accepted answer. – Melllvar Jul 25 '17 at 21:25
  • 1
    Maps are not safe for concurrent access, you need a mutex or some other mechanism to prevent more than one goroutine from modifying it while iterating over it. – Vinay Pai Jul 26 '17 at 17:31
  • 1
    I disagree with @DustyW, the append version reads better, is more concise and doesn't require a counter variable. – John Eikenberry Mar 18 '18 at 19:12
  • 2
    Whatever floats your boat. I personally would rather write code that is two lines longer but more efficient. – Vinay Pai Mar 19 '18 at 20:13
  • 1
    I concur with Vinay on efficiency efforts. In this vein, use this tool to spot pre-alloc opportunities in your code: https://github.com/alexkohler/prealloc – colm.anseo Apr 10 '18 at 15:19
  • Concurrency can bring some troubles here: if the map size grows while the copy is beeing made, you can end up trying to insert itens past the array limit. In this case, the append solution or the reflect one is better. – Atila Romero Aug 08 '18 at 12:27
  • Maps are not safe for concurrent access. You need to rethink the whole solution to make it safe if that's a possibility. – Vinay Pai Aug 08 '18 at 21:58
  • 5
    @Alaska if you're concerned with the cost of allocating one temporary counter variable, but think a function call is going to take less memory you should educate yourself on what actually happens when a function gets called. Hint: It's not a magical incantation that does things for free. If you think the currently accepted answer is safe under concurrent access, you also need to go back to the basics: https://blog.golang.org/go-maps-in-action#TOC_6. – Vinay Pai Oct 08 '18 at 23:31
  • 1
    Anyone reading this after go 1.10.1 it seems like there's no significant different between assignment and using append https://stackoverflow.com/a/52148380 – Uri Apr 21 '20 at 19:19
304

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.

peterSO
  • 158,998
  • 31
  • 281
  • 276
  • 46
    It's slightly better to set the actual size instead of capacity and avoid append altogether. See my answer for details. – Vinay Pai Jan 08 '15 at 19:37
  • 3
    Note that if `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. – Melllvar Jul 25 '17 at 21:23
  • 18
    maps are not safe under concurrent access, neither solution is acceptable if another goroutine might change the map. – Vinay Pai Jul 26 '17 at 17:27
  • @VinayPai it is okay to read from a map from multiple goroutines but not write – darethas Dec 01 '17 at 02:38
  • 6
    @darethas that is a common misconception. The race detector will flag this usage since 1.6. From the release notes: "As always, if one goroutine is writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program." https://golang.org/doc/go1.6#runtime – Vinay Pai Aug 08 '18 at 22:10
  • @vinay-pai Correct, I shouldn't have said okay but safe. Now whether that will result in correct behavior from your program is another story, which you are right. – darethas Aug 09 '18 at 14:57
  • 1
    @darethas why is this sometimes safe to do and sometimes a problem? From Vinay Pais comment it seems like this is a bad idea in general? – xuiqzy Sep 09 '20 at 09:04
  • I'm not a Go expert yet, but I am a computer expert. I cannot find myself convinced that using append instead of an index is any more efficient, and certainly should not prevent unnecessary memory allocations. Although, I do understand that the "append" mechanism in Go on a slice allocated with a sufficient capacity is supposed to be as efficient (or nearly so?) as using the index. I would actually imagine it (nearly immeasurably) _less_ efficient, since append would have to change len of the slice on each iteration. Then again, the compiler can unroll that part of the loop. – Daniel Santos Feb 09 '21 at 22:56
  • Has there been a discussion in the Go community about why not add a function similar to Python's `keys` to make code more modular and shorter? There could even be a thread-safe and a non-thread-safe version of it. – Olshansky Jan 01 '22 at 16:05
124

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]
}
Zakhar
  • 605
  • 8
  • 17
Denis Kreshikhin
  • 8,856
  • 9
  • 52
  • 84
  • 1
    I think this is a good approach if there is a chance of a concurrent map access: this won't panic if the map grows during the loop. About performance, I'm not really sure, but I suspect it outperforms the append solution. – Atila Romero Aug 08 '18 at 16:28
  • @AtilaRomero Not sure that this solution has any advantages, but when use reflection for any purposes this is more useful, because it allows to take a key as Value typed directly. – Denis Kreshikhin Aug 17 '18 at 09:50
  • 42
    Is there a way to convert this to `[]string`? – Doron Behar May 29 '19 at 15:13
  • 1
    @AtilaRomero I just tested it, it still panics when the list grows. – gtato Oct 07 '20 at 19:13
  • 1
    This is the answer i was looking for. I am trying merge two different "Entity" structs, both of which contain a map[string][]*Foo. So, for the first struct, i need to identify the keys, so i can look it up in the second struct by key, instead of ranging over every map value to see if its they key i want to merge into. Thanks! – Jeremy Giaco Jun 29 '21 at 13:22
  • if you need a `[]string` look for blackgreen's answer with generics. `maps.Keys` – rufreakde Jan 24 '23 at 11:34
  • This is a good answer. But also please consider that reflect package should not be used in production code. It's not a good choice. – Amin Shojaei Feb 14 '23 at 15:27
79

Go now has generics. You can get the keys of any map with 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
}
blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • What is the meaning of `~` in `~map[K]V`? – Marko Jul 18 '22 at 07:10
  • 3
    @Marko please see [What's the meaning of the new tilde token ~ in Go?](https://stackoverflow.com/questions/70888240/whats-the-meaning-of-the-new-tilde-token-in-go) – blackgreen Jul 18 '22 at 07:14
24

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.)

Nico Villanueva
  • 876
  • 8
  • 22
  • 5
    In your `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. – keithbhunter Apr 01 '19 at 15:59
  • @keithbhunter I agree, there is very little difference. – Enver Bisevac Mar 24 '21 at 14:18
  • @keithbhunter Calling make([]int, len(m)) and make([]int, 0, len(m) are effectivelly the same thing: preallocates array in memory, which would completely defeats the purpose of the test. – rdnobrega Apr 29 '21 at 17:10
18

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.

  • 18
    It is less efficient than the original though - append will do multiple allocations to grow the underlying array, and has to update the slice length every call. Saying `keys = make([]int, 0, len(mymap))` will get rid of the allocations but I expect it will still be slower. – Nick Craig-Wood Jan 26 '14 at 14:53
  • 1
    This answer is safer than using len(mymap), if someone else changes the map while the copy is beeing made. – Atila Romero Aug 08 '18 at 12:21
  • @AtilaRomero is that true? I would assume that in such case, data may be corrupted completely, or is this specified somewhere in golang? – Petr Sep 06 '21 at 17:48
  • 1
    @Petr I think that should panic. There should not be two routines working on the same map. That's what sync.Map should be used for, or use a map with Mutexes – Matias Barrios Nov 09 '21 at 04:17
  • Concurrent access would cause a data race error, if testing with that enabled. Definitely something to avoid. – Jeff Learman Jun 24 '22 at 17:55
3

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
}
Eric
  • 22,183
  • 20
  • 145
  • 196
2

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

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]
Saurabh
  • 5,176
  • 4
  • 32
  • 46
  • 1
    It should be noted that the order of results from both maps.Keys and maps.Values are indeterminate, so the order of results from one cannot be assumed to match the other. – Evan Byrne Jun 29 '23 at 02:40
1

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

Keys

Creates an array of the map keys.

keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2})
// []string{"bar", "foo"}

Values

Creates an array of the map values.

values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
Alex Kosh
  • 2,206
  • 2
  • 19
  • 18