samol
samol

Reputation: 20590

How to iterate through a map in Golang in order?

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

Upvotes: 113

Views: 144603

Answers (8)

Vadim K.
Vadim K.

Reputation: 2446

Recent versions of Go support this alternative:

for _, key := range slices.Sorted(maps.Keys(romanNumeralDict)) {
    fmt.Println("k:", key, "v:", romanNumeralDict[key])
}

Upvotes: 2

Ceri Davies
Ceri Davies

Reputation: 1

package main

// If this is an exercise in how to iterate over maps in order, then
// this is not answering that, but its a simpler alternative for the 
// original question, as it avoids the complexity of using a map. So 
// don't use a map, just use a slice, then it stays in order:

import (
    "fmt"
)

type romanEntry struct {
    roman string
    number int
}

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

func main() {
    for j := 0; j < len(romanTranslationTable); j++ {
        fmt.Printf("Roman: %s\n", romanTranslationTable[j].roman)
    }
}

Upvotes: 0

Timmmm
Timmmm

Reputation: 96655

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
}

Edit 2: Go 1.21 the Ordered interface above can be replaced with cmp.Ordered

Playground example

Upvotes: 49

Lo&#239;c Madi&#232;s
Lo&#239;c Madi&#232;s

Reputation: 337

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

Upvotes: 1

Brent Bradburn
Brent Bradburn

Reputation: 54919

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)

Upvotes: 4

morganbaz
morganbaz

Reputation: 3117

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.

Upvotes: 1

1218985
1218985

Reputation: 8022

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

Upvotes: 1

Volker
Volker

Reputation: 42422

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

Upvotes: 157

Related Questions