95

Please see below my map

var romanNumeralDict map[int]string = map[int]string{
  1000: "M",
  900 : "CM",
  500 : "D",
  400 : "CD",
  100 : "C",
  90  : "XC",
  50  : "L",
  40  : "XL",
  10  : "X",
  9   : "IX",
  5   : "V",
  4   : "IV",
  1   : "I",
}

I am looking to loop through this map in the order of the size of the key

  for k, v := range romanNumeralDict {
    fmt.Println("k:", k, "v:", v)
  }

However, this prints out

k: 1000 v: M
k: 40 v: XL
k: 5 v: V
k: 4 v: IV
k: 900 v: CM
k: 500 v: D
k: 400 v: CD
k: 100 v: C
k: 90 v: XC
k: 50 v: L
k: 10 v: X
k: 9 v: IX
k: 1 v: I

Is there a way that I can print them out in the order of the size of the key so, I would like to loop through this map like this

k:1
K:4
K:5
K:9
k:10

etc...

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
samol
  • 18,950
  • 32
  • 88
  • 127
  • You'll need to loop through, add the pairs to a slice, and sort the slice. – Chris Pfohl Aug 20 '13 at 18:49
  • See http://stackoverflow.com/questions/12108215/golang-map-prints-out-of-order – Ray Toal Aug 20 '13 at 18:53
  • 14
    According to the [spec](http://golang.org/ref/spec#For_statements), "The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next." The Go authors did even intentionally randomize the iteration sequence (i.e. they use a random number generator so that each range statement yields a distinct ordr) so nobody incorrectly depends on any interation order. (What happens if you depend on the order being different each time? hm...) – fuz Aug 20 '13 at 19:51
  • 2
    See the last section, **Iteration order**, in _[Go maps in action](https://blog.golang.org/go-maps-in-action)_ by Andrew Gerrand. – legends2k Apr 03 '18 at 16:37
  • 2
    Possible duplicate of [sort golang map values by keys](https://stackoverflow.com/questions/23330781/sort-golang-map-values-by-keys) – Jonathan Hall Jul 08 '18 at 09:26
  • Use treemap: https://pkg.go.dev/github.com/emirpasic/gods/maps/treemap – wintermute Jul 05 '22 at 22:27

7 Answers7

138

Collect all keys, sort them and iterate your map by key, like the following:

keys := make([]int, 0)
for k, _ := range romanNumeralDict {
    keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
    fmt.Println(k, romanNumeralDict[k])
}
Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
Volker
  • 40,468
  • 7
  • 81
  • 87
44

You can make it a little faster by preallocating keys because you know its length:

func sortedKeys(m map[Key]Value) ([]Key) {
        keys := make([]Key, len(m))
        i := 0
        for k := range m {
            keys[i] = k
            i++
        }
        sort.Keys(keys)
        return keys
}

Replace Key and Value with your key and value types (including the sort line). cough generics cough

Edit: Go 1.18 is finally getting generics! Here's the generic version:

// Ordered is a type constraint that matches any ordered type.
// An ordered type is one that supports the <, <=, >, and >= operators.
//
// Note the generics proposal suggests this type will be available from
// a standard "constraints" package in future.
type Ordered interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

func sortedKeys[K Ordered, V any](m map[K]V) ([]K) {
        keys := make([]K, len(m))
        i := 0
        for k := range m {
            keys[i] = k
            i++
        }
        sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
        return keys
}

Playground example

ItalyPaleAle
  • 7,185
  • 6
  • 42
  • 69
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • Nice improvement. I was going to suggest the same. And maybe even suggest using a _cough_ `interface{}` _cough_ so generics wouldn't be needed. :-) – MikeSchinkel Jan 04 '19 at 16:02
  • Is it possible to re-write this so at least Value is generic (when Key is a string)? – Setjmp Jan 31 '19 at 17:32
  • @SetJmp: No, it is not. In Go every map or slice is a different type. Your best bet would be to accept an `interface{}` type and use reflection to handle it. – Ricardo Souza Jul 10 '19 at 19:05
  • the `constraints` package is confirmed in Go 1.18 – blackgreen Jan 12 '22 at 16:12
  • is it me or am i the only one having an error with the `type` before the int `expected '}', found 'type'syntax` – will.mendil Sep 12 '22 at 15:17
  • That code was for the pre-release version so it might have changed a bit. Also `Ordered` [is available in the standard library](https://pkg.go.dev/golang.org/x/exp/constraints#Ordered) so you don't need to define it yourself. Feel free to update the answer if you get it working. – Timmmm Sep 13 '22 at 11:55
2

You can get a sortable array of keys using MapKeys.

In this example, the keys are of type string:

keys := reflect.ValueOf(myMap).MapKeys()
keysOrder := func(i, j int) bool { return keys[i].Interface().(string) < keys[j].Interface().(string) }
sort.Slice(keys, keysOrder)

// process map in key-sorted order
for _, key := range keys {
    value := myMap[key.Interface().(string)]
    fmt.Println(key, value)
}
  • See: Getting a slice of keys from a map
  • Warning: This bypasses some compile-time type-safety (panics if not a map)
  • You'll need to cast each key in order to get its raw value: key.Interface().(string)
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • 2
    +1. BTW perhaps worth mentioning that without the `sort.Slice(keys, keysOrder)` the order returned is not only not sorted it's not even deterministic between runs. – k1eran Apr 27 '20 at 16:13
1

You can iterate over the map in order by sorting the keys explicitly first, and then iterate over the map by key. Since you know the final size of keys from the romanNumeralDict outset, it is more efficient to allocate an array of the required size up front.

// Slice for specifying the order of the map.
// It is initially empty but has sufficient capacity
// to hold all the keys of the romanNumeralDict map.
keys := make([]int, 0, len(romanNumeralDict))

// Collect keys of the map
i := 0
for k, _ := range romanNumeralDict {
    keys[i] = k
    i++
}

// Ints sorts a slice of ints in increasing order
sort.Ints(keys)

// Iterate over the map by key with an order
for _, k := range keys {
    fmt.Println(k, romanNumeralDict[k])
}
1218985
  • 7,531
  • 2
  • 25
  • 31
1

Based on @Brent's answer, I had an occasion where I wanted sorted map keys in a non-critical piece of code, without having to repeat myself too much. So here is a starting point to make a generic map-iteration function for many different types:

func sortedMapIteration(m interface{}, f interface{}) {
    // get value and keys
    val := reflect.ValueOf(m)
    keys := val.MapKeys()
    var sortFunc func(i, j int) bool
    kTyp := val.Type().Key()

    // determine which sorting function to use for the keys based on their types.
    switch {
    case kTyp.Kind() == reflect.Int:
        sortFunc = func(i, j int) bool { return keys[i].Int() < keys[j].Int() }
    case kTyp.Kind() == reflect.String:
        sortFunc = func(i, j int) bool { return keys[i].String() < keys[j].String() }
    }
    sort.Slice(keys, sortFunc)

    // get the function and call it for each key.
    fVal := reflect.ValueOf(f)
    for _, key := range keys {
        value := val.MapIndex(key)
        fVal.Call([]reflect.Value{key, value})
    }
}

// example:
func main() {
    sortedMapIteration(map[string]int{
        "009": 9,
        "003": 3,
        "910": 910,
    }, func(s string, v int) {
        fmt.Println(s, v)
    })
}

playground

To stress: this code is inefficient and uses reflection, so it does not have compile-time type safety, and a generic implementation should have more type safeguards and handle more key types. However, for quick and dirty scripts this can help you get started. You will need to add more cases to the switch block, according to which key types you expect to pass.

morganbaz
  • 2,997
  • 1
  • 17
  • 30
0

You can also use this package https://github.com/wk8/go-ordered-map. Performance/memory usage about the package needs to be bench but it seems to answer to the need.

It works like a map but can be iterated like this:

for pair := om.Oldest(); pair != nil; pair = pair.Next() {
        fmt.Printf("%d => %s\n", pair.Key, pair.Value.payload)
}
Loïc Madiès
  • 277
  • 1
  • 5
  • 19
0

Short version with Go 1.21:

m := make(map[string]string)
// fill the map
keys := maps.Keys(m)
slices.Sort(keys)
for k := range kyes {
    fmt.Println(m[k])
}
Artem Klevtsov
  • 9,193
  • 6
  • 52
  • 57