How to iterate over and order a map in Go
You can iterate over maps in Go but, unlike slices, the order of iteration is not guaranteed. That’s because a map is explictly defined to be unordered in the Go Specification so the runtime deliberately randomises its iteration order to ensure that developers are aware of its unordered nature. Having said that, there are ways to get around this limitation, and that is what will be addressed in this article.
The idiomatic way to iterate over a map in Go is by using the for..range
loop
construct. Instead of receiving index/value pairs as with slices, you’ll get
key/value pairs with maps. The iteration order is intentionally randomised when
you use this technique.
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
for key, value := range m {
fmt.Println(key, "->", value)
}
}
To guarantee a specific iteration order, you need to create some additional
data structures and use a method from the sort
package to control the order of
iteration.
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
// Create a struct for each map key-value pair
// Make sure the types match accordingly
type KeyValue struct {
Key string
Value int
}
// create an empty slice of key-value pairs
s := make([]KeyValue, 0, len(m))
// append all map keys-value pairs to the slice
for k, v := range m {
s = append(s, KeyValue{k, v})
}
}
A KeyValue
struct is used to hold the values for each map key-value pair. This
struct is placed in a slice whose initial capacity is set to the length of the
map in question. Now that we have a slice of KeyValue
structs, we can use the
SortStable()
method from the sort
package to sort the slice in any way we
please. Once the slice is sorted accordingly, you can iterate over it to get the
desired order.
Here’s an example that sorts the slice of structs by value so that the slice is ordered in such a way that the greater numbers come before the lesser ones.
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
// Create a struct for each map key-value pair
// Make sure the types match accordingly
type KeyValue struct {
Key string
Value int
}
// create an empty slice of key-value pairs
s := make([]KeyValue, 0, len(m))
// append all map keys-value pairs to the slice
for k, v := range m {
s = append(s, KeyValue{k, v})
}
// sort the slice of key-value pairs by value in descending order
sort.SliceStable(s, func(i, j int) bool {
return s[i].Value > s[j].Value
})
// iterate over the slice to get the desired order
for _, v := range s {
fmt.Println(v.Key, "->", v.Value)
}
}
If you want to sort the map by key, you only need to change the function
argument to SliceStable()
so it sorts by the struct’s Key
instead of
Value
.
Conclusion
As you can see, it’s not impossible to sort a map in Go although it’s not the most convenient thing to do. Ensure to check out this article on sorting in Go in case you require a more complex sorting algorithm to achieve the desired order.
Thanks for reading, and happy coding!