ChatGPT解决这个技术问题 Extra ChatGPT

Getting a slice of keys from a map

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

F
Freedom_Ben

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.


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.
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 :)
Why aren't you using an index with the range, for i, k := range mymap{. That way you don't need the i++?
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.
@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: blog.golang.org/go-maps-in-action#TOC_6.
p
peterSO

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.


It's slightly better to set the actual size instead of capacity and avoid append altogether. See my answer for details.
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.
maps are not safe under concurrent access, neither solution is acceptable if another goroutine might change the map.
@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." golang.org/doc/go1.6#runtime
@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?
Z
Zakhar

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

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.
@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.
Is there a way to convert this to []string?
@AtilaRomero I just tested it, it still panics when the list grows.
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!
b
blackgreen

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, you can copy the source code:

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

What is the meaning of ~ in ~map[K]V?
N
Nico Villanueva

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


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 I agree, there is very little difference.
@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.
佚名

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.


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.
This answer is safer than using len(mymap), if someone else changes the map while the copy is beeing made.
@AtilaRomero is that true? I would assume that in such case, data may be corrupted completely, or is this specified somewhere in golang?
@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
Concurrent access would cause a data race error, if testing with that enabled. Definitely something to avoid.
L
Lalit Sharma

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

E
Eric

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
}