什么是在地图中测试和处理多个键的惯用Go方式?

时间:2017-10-02 20:50:50

标签: dictionary go

我有map[string]string,我需要测试是否存在某些键,如果存在,则将某些值转换为整数。例如:

m := map[string]string{"a": "b", "c": "d", "e": "f"}
if v1, ok := m["a"]; ok {
  if v2, ok := m["c"]; ok {
     if i1, err := strconv.Atoi(v1); err != nil {
       if i2, err := strconv.Atoi(v2); err != nil {
         // do something with i1, i2
       }
     }
  }
}

我发现自己的想法是:

if m.exists("a") && m.exists("c") && is_int(m["a"]) && is_int(m["c"]) {
   // do something with atoi(m["a"]) and atoi(m["c"])
}

...但是使用Go的标准库并不方便。那么,Go的做法是什么?

2 个答案:

答案 0 :(得分:4)

测试string值是否为数字并将其转换为数字基本上是相同的工作量。

所以我宁愿使用以下的util函数,除了检查键和测试值之外,还返回转换后的数字:

func getInts(m map[string]string, keys ...string) (is []int, err error) {
    for _, k := range keys {
        v, ok := m[k]
        if !ok {
            return is, fmt.Errorf("%s is missing", k)
        }
        var i int
        if i, err = strconv.Atoi(v); err != nil {
            return
        }
        is = append(is, i)
    }
    return
}

用3种不同的情况进行测试:

maps := []map[string]string{
    {},
    {"a": "b", "c": "d", "e": "f"},
    {"a": "1", "c": "2", "e": "f"},
}

for _, m := range maps {
    if is, err := getInts(m, "a", "c"); err == nil {
        fmt.Println("Numbers:", is)
    } else {
        fmt.Println("Error:", err)
    }
}

输出(在Go Playground上尝试):

Error: a is missing
Error: strconv.Atoi: parsing "b": invalid syntax
Numbers: [1 2]

答案 1 :(得分:2)

一种方法是创建自己的map[string]string类型并添加一些简单的方法。这是一个简单的例子:

type mapExists map[string]string

func (m mapExists) exists(key string) bool {
    _, ok := m[key]
    return ok
}

func (m mapExists) isInt(key string) bool {
    v, ok := m[key]
    if !ok {
        return false
    }

    _, err := strconv.ParseInt(v, 10, 64)
    return err == nil
}

您可以使用以下内容将map[string]string转换为mapExists

m := map[string]string{
    "a": "b",
    "c": "2",
}

m2 := mapExists(m)

然后调用你的例子中的东西:

fmt.Println("a exists:\t", m2.exists("a"))
fmt.Println("asd exists:\t", m2.exists("asd"))
fmt.Println("a isInt:\t", m2.isInt("a"))
fmt.Println("c isInt:\t", m2.isInt("c"))

将输出:

a exists:        true
asd exists:      false
a isInt:         false
c isInt:         true

这是“最佳”还是“惯用”方式是一个意见问题,取决于具体情况。一般来说,我(个人)更愿意使用这些包装器。您可以将上面的示例重写为以下内容:

func test() {
    m := map[string]string{"a": "b", "c": "d", "e": "f"}

    v1, v1ok := m["a"]
    v2, v2ok := m["c"]
    if !v1ok || !v2ok {
       return
    }

    i1, err := strconv.Atoi(v1)
    if err != nil {
       return
    }
    i2, err := strconv.Atoi(v2) {
    if err != nil {
       return
    }
}

我认为这更具可读性。它更冗长,但Go是一种冗长的语言; - )

但同样,它取决于整个代码库。如果您经常调用此模式,那么自定义类型可能是一个很好的解决方案(代价是性能略低,这可能是也可能不是问题)。