关于dictionary:如果map包含一个键,怎么检查?

How to check if a map contains a key in Go?

我知道我可以迭代地图m

1
for k, v := range m { ... }

寻找一把钥匙,但是有没有一种更有效的方法来测试一把钥匙在地图上的存在?

我在语言规范中找不到答案。


一行回答:

1
2
3
if val, ok := dict["foo"]; ok {
    //do something here
}

说明:

go中的if语句可以同时包含条件和初始化语句。上面的示例同时使用这两种方法:

  • 初始化两个变量-val将从映射中接收"foo"的值或"零值"(在本例中为空字符串),如果映射中实际存在"foo",ok将接收一个bool,该bool将被设置为true

  • 评估ok,如果"foo"在地图中,则为true

如果地图中确实存在"foo",将执行if语句的主体,并且val将是该范围的局部。


编辑:以下答案在Go 1之前。从Go 1开始,它不再准确/有效。

除了Go编程语言规范,您还应该阅读有效的Go。在地图部分,他们说,除了其他事情:

试图用映射中不存在的键获取映射值将导致程序崩溃,但有一种方法可以安全地使用多重分配。

1
2
3
var seconds int
var ok bool
seconds, ok = timeZone[tz]

"为了测试在地图中的存在而不担心实际值,可以使用空白标识符,一个简单的下划线)。空白标识符可以被分配或声明为任何类型的值,而该值被无害地丢弃。若要测试在地图中的存在,请使用空白标识符代替该值的普通变量。

1
_, present := timeZone[tz]


在Go Nuts邮件列表中搜索,发现了一个由Peter Froehlich于2009年11月15日发布的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import"fmt"

func main() {
        dict := map[string]int {"foo" : 1,"bar" : 2}
        value, ok := dict["baz"]
        if ok {
                fmt.Println("value:", value)
        } else {
                fmt.Println("key not found")
        }
}

或者,更紧凑地说,

1
2
3
4
5
if value, ok := dict["baz"]; ok {
    fmt.Println("value:", value)
} else {
    fmt.Println("key not found")
}

注意,使用这种形式的if语句,valueok变量仅在if条件内可见。


简短回答

1
2
_, exists := timeZone[tz]    // Just checks for key existence
val, exists := timeZone[tz]  // Checks for key existence and retrieves the value

例子

下面是围棋场的一个例子。

更长的答案

根据有效GO的"地图"部分:

An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0.

Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for"UTC" or is that the empty string because it's not in the map at all? You can discriminate with a form of multiple assignment.

1
2
3
var seconds int
var ok bool
seconds, ok = timeZone[tz]

For obvious reasons this is called the"comma ok" idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report:

1
2
3
4
5
6
7
func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

To test for presence in the map without worrying about the actual value, you can use the blank identifier (_) in place of the usual variable for the value.

1
_, present := timeZone[tz]

如其他答案所述,一般的解决方案是在特殊形式的赋值中使用索引表达式:

1
2
3
4
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]

这个又漂亮又干净。但是它有一些限制:它必须是一个特殊形式的分配。右侧表达式只能是映射索引表达式,左侧表达式列表必须正好包含两个操作数,第一个操作数的值类型是可分配的,第二个操作数的值类型是可分配的。这个特殊形式索引表达式的结果的第一个值将是与键关联的值,第二个值将告诉映射中是否存在具有给定键的条目(如果该键存在于映射中)。如果不需要一个结果,则左侧表达式列表还可以包含空白标识符。

重要的是要知道,如果索引映射值是nil或不包含键,则索引表达式将计算为映射值类型的零值。例如:

1
2
3
4
5
6
m := map[int]string{}
s := m[1] // s will be the empty string""
var m2 map[int]float64 // m2 is nil!
f := m2[2] // f will be 0.0

fmt.Printf("%q %f", s, f) // Prints:"" 0.000000

在运动场上试试。

所以如果我们知道在我们的地图中不使用零值,我们可以利用这个。

例如,如果值类型为string,并且我们知道我们从不在映射中存储值为空字符串的条目(string类型的零值),我们还可以通过将索引表达式的非特殊形式与零值进行比较来测试键是否在映射中:

1
2
3
4
5
6
7
8
9
m := map[int]string{
    0:"zero",
    1:"one",
}

fmt.Printf("Key 0 exists: %t
Key 1 exists: %t
Key 2 exists: %t",
    m[0] !="", m[1] !="", m[2] !="")

输出(在游乐场上尝试):

1
2
3
Key 0 exists: true
Key 1 exists: true
Key 2 exists: false

在实践中,有很多情况下,我们不在地图中存储零值,所以这可以经常使用。例如,接口和函数类型有一个零值nil,我们通常不将其存储在映射中。因此,可以通过将密钥与nil进行比较来测试它是否在映射中。

使用这种"技术"还有另一个优点:您可以用一种简洁的方式检查多个键的存在(您不能用特殊的"逗号确定"形式来检查)。有关此的详细信息:检查在一个条件下多个映射中是否存在键


这里更好的方法

1
2
3
if _, ok := dict["foo"]; ok {
    //do something here
}


它在"索引表达式"中提到。

An index expression on a map a of type map[K]V used in an assignment
or initialization of the special form

1
2
3
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

yields an additional untyped boolean value. The value of ok is true if
the key x is present in the map, and false otherwise.


1
2
3
4
5
6
7
8
9
10
11
    var empty struct{}
    var ok bool
    var m map[string]struct{}
    m = make(map[string]struct{})
    m["somestring"] = empty


    _, ok = m["somestring"]
    fmt.Println("somestring exists?", ok)
    _, ok = m["not"]
    fmt.Println("not exists?", ok)

然后,去运行地图。是否存在字符串?真不存在?假


为此,可以使用双值分配。请检查下面的示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
   "fmt"
)

func main() {
    //creating a map with 3 key-value pairs
    sampleMap := map[string]int{"key1": 100,"key2": 500,"key3": 999}
    //A two value assignment can be used to check existence of a key.
    value, isKeyPresent := sampleMap["key2"]
    //isKeyPresent will be true if key present in sampleMap
    if isKeyPresent {
        //key exist
        fmt.Println("key present, value = ", value)
    } else {
        //key does not exist
        fmt.Println("key does not exist")
    }
}

只使用

1
2
3
if len(m) == 0 {
    ...
}