RhinoLarva
RhinoLarva

Reputation: 815

How to get sorted list of map keys in Go?

Let's imagine I have a map: map[string]string. I would like to get the list of sorted keys for this map. So I could do something like this:

func SortedMapKeys(m map[string]string) (keyList []string) {
    for key := range m {
        keyList = append(keyList, key)
    }
    sort.Strings(keyList)
    return
}

Then I will have another map of type map[string]bool. I would like to get it's keys also. But the problem is that function SortedMapKeys accepts a map[string]string argument. So I need to write exactly the same function, with the only one difference - it will accept a map[string]bool.

For obvious reasons it's not an option. If one day I would like to change the logic of how I get and sort my keys, I will need to track and update all these functions. Also, I will have to write the same unit tests for all these functions which actually do the same thing since their bodies are 100% equal (code duplicate).

Is there any way to create a generic function which could accept an map[string] of whatever?

Upvotes: 8

Views: 8200

Answers (4)

Vadim K.
Vadim K.

Reputation: 2456

Recent versions of Go support this gem:

theSortedSliceOfKeys := slices.Sorted(maps.Keys(theMap))

Upvotes: 4

Barkha
Barkha

Reputation: 722

Just adding in case some one is using new version of Go >=1.18. We can use generics for cases like these

func main() {
    s := map[string]string{
        "s1": "t",
        "s2": "t1",
    }

    b := map[string]bool{
        "b1": true,
        "b2": true,
    }
    k := SortedKeys(s)
    fmt.Printf("map keys %v", k)

    k = SortedKeys(b)
    fmt.Printf(" map keys %v", k)
}

func SortedKeys[V string | bool](m map[string]V) []string {
    keys := maps.Keys(m)
    sort.Strings(keys)
    return keys
}

Link to go playground https://go.dev/play/p/2nHT8lt--Fj

Upvotes: 1

kopiczko
kopiczko

Reputation: 3058

Here's my 0.02$. Since keys extracting logic is unlikely to change and you want to keep everything in one place you can create variant and choose non-nil map from it:

type MapVariant struct {
    Bool   map[string]bool
    String map[string]string
}

func SortedMapKeys(variant MapVariant) (keyList []string) {
    if variant.String != nil {
        for k := range variant.String {
            keyList = append(keyList, k)
        }
        goto SORT
    }
    if variant.Bool != nil {
        for k := range variant.Bool {
            keyList = append(keyList, k)
        }
        goto SORT
    }

SORT:
    sort.Strings(keyList)
    return
}

Of course you can avoid goto statements by adding more conditions but I personally find it clearer.

Then you can use the function like:

SortedMapKeys(MapVariant{
    Bool: map[string]bool{"a": true, "b": false}
})
SortedMapKeys(MapVariant{
    String: map[string]string{"c": "v1", "b": "v2"}
})

Upvotes: 1

Mr_Pink
Mr_Pink

Reputation: 109442

Since map[string]bool and map[string]string and map[string]Whatever are all distinct types, the only way to create a single function to sort the keys of all possible map[string]* types is via reflection.

func SortedMapKeys(m interface{}) (keyList []string) {
    keys := reflect.ValueOf(m).MapKeys()

    for _, key := range keys {
        keyList = append(keyList, key.Interface().(string))
    }
    sort.Strings(keyList)
    return
}

For an in-between solution, since there are probably only a few combinations of types you're concerned with, you can use a type switch to extract the keys

func SortedMapKeys(m interface{}) (keyList []string) {
    switch m := m.(type) {
    case map[string]string:
        for k := range m {
            keyList = append(keyList, k)
        }
    case map[string]bool:
        for k := range m {
            keyList = append(keyList, k)
        }
    default:
        panic("unknown map type")
    }

    sort.Strings(keyList)
    return
}

Upvotes: 6

Related Questions