3

I would like to get the keys of an arbitrary map variable. And I found this function, which replicates the PHP array_keys behaviour: https://www.php2golang.com/method/function.array-keys.html. But the following code:

items := map[string]int{
    "one":   1,
    "two":   2,
    "three": 3,
}
keys := ArrayKeys(items)

throws the next 'compile time' exception:

cannot use items (type map[string]int) as type map[interface {}]interface {} in argument to ArrayKeys

what am I doing wrong?

Here's an example: https://play.golang.org/p/0ImqjPJFFiE

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Cequiel
  • 3,505
  • 6
  • 27
  • 44
  • Please post the code where you are using the maps in golang. Show the definition of `ArrayKeys` function. – Himanshu Aug 21 '18 at 17:50
  • Here it is: https://play.golang.org/p/0ImqjPJFFiE – Cequiel Aug 21 '18 at 17:52
  • 2
    you will have to specialize it for each type you want to iterate over. One generic solution involves usage of the reflection package, but it is noticeably slower. –  Aug 21 '18 at 18:03
  • 2
    about reflection package usage, see https://stackoverflow.com/a/25773086/4466350 –  Aug 21 '18 at 18:06

3 Answers3

4

You should change the function to work with your types:

func ArrayKeys(elements map[string]int) []string  {
    i, keys := 0, make([]string, len(elements))
    for key, _ := range elements {
        keys[i] = key
        i++
    }
    return keys
}

https://play.golang.org/p/n5kZego0ePY

But why should you change the signature?

If a function in Go takes an empty interface all the including variables has no type any more. But Go is strongly typed, so dealing with variables without a type is difficult. If you provide a function like yours the code should deal really with any type. To do so you would need to use the reflection package. And if you have an error that you missed something your program will panic, when the error occurs. When you writing a server it can run for months and stop running then.

If you write an ArrayKey function for every type you have the things are different. The compiler ensures that your code is robust and will never panic at that point. If you want to try to use the function with another type of map the code will not compile. That is the way Go works.

apxp
  • 5,240
  • 4
  • 23
  • 43
2

In Go, write a simple function. For example,

package main

import "fmt"

func main() {

    items := map[string]int{
        "one":   1,
        "two":   2,
        "three": 3,
    }

    keys := func(m map[string]int) []string {
        mk := make([]string, 0, len(m))
        for k := range m {
            mk = append(mk, k)
        }
        return mk
    }(items)

    fmt.Printf("%d %q\n", len(keys), keys)
}

Playground: https://play.golang.org/p/B0kOxAGbQCZ

Output:

3 ["three" "one" "two"]

For sorted keys,

package main

import (
    "fmt"
    "sort"
)

func main() {

    items := map[string]int{
        "one":   1,
        "two":   2,
        "three": 3,
    }

    keys := func(m map[string]int) []string {
        mk := make([]string, 0, len(m))
        for k := range m {
            mk = append(mk, k)
        }
        sort.Strings(mk)
        return mk
    }(items)

    fmt.Printf("%d %q\n", len(keys), keys)
}

Playground: https://play.golang.org/p/IQLx7gjGk8j

Output:

3 ["one" "three" "two"]
peterSO
  • 158,998
  • 31
  • 281
  • 276
0

The error clearly mentioned:

cannot use items (type map[string]int) as type map[interface {}]interface {} in argument to ArrayKeys

Because of mismatched types you are getting above error. If you want to use any type in the function you can go for interface because interface{} is not equal to map[interface{}]interface{}

You can go for interface{} to wrap the data coming from maps if you really want to use interface. Else you should use the same type as an argument to the function as mentioned in above answers.

package main

import (
    "fmt"
)

func ArrayKeys(elements interface{}) []interface{} {
    i, keys := 0, make([]interface{}, len(elements.(interface{}).(map[string]int)))
    for key, _ := range elements.(interface{}).(map[string]int) {
        keys[i] = key
        i++
    }
    return keys
}

func main() {
    items := map[string]int{
        "one":   1,
        "two":   2,
        "three": 3,
    }
    keys := ArrayKeys(items)
    fmt.Println(keys)
}

Working code on Go Playground

Himanshu
  • 12,071
  • 7
  • 46
  • 61
  • Bad idea. Panics with another type: https://play.golang.org/p/ZNo263ktae5 – apxp Aug 21 '18 at 18:08
  • @apxp try that option with your anser that also it will panic :). Because you have changed the type it is common and basics. and it is obvious too. you are creating a different type and then saying my code panic ofcourse your will panics to in that case. – Himanshu Aug 21 '18 at 18:11
  • The difference is that if you have the the type as input the compiler makes your function safe. Using my function witch a different type the code does not compile. If a code compiles and gives in another case a panic that code is not safe for production. If you use type conversions inside your function you have to check everytime your conversion and handle a different type. – apxp Aug 21 '18 at 18:12
  • @apxp if you check my answer I have strictly mentioned that if you want to use interface then you can use it like I have given. I have edited my answer with an option on how to use interface in OP case. – Himanshu Aug 21 '18 at 18:18