从地图中获取密钥切片

Getting a slice of keys from a map

提问人:Saswat Padhi 提问时间:1/26/2014 最后编辑:blackgreenSaswat Padhi 更新时间:9/21/2023 访问量:451277

问:

有没有更简单/更好的方法可以从 Go 中的地图中获取一部分键?

目前,我正在遍历地图并将键复制到切片:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
字典 切片

评论


答:

18赞 user1804599 1/26/2014 #1

一个更好的方法是使用:append

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

除此之外,你就不走运了——Go 不是一种非常富有表现力的语言。

评论

18赞 Nick Craig-Wood 1/26/2014
不过,它的效率不如原来的 - append 将进行多次分配以增加底层数组,并且每次调用都必须更新切片长度。说会摆脱分配,但我预计它仍然会更慢。keys = make([]int, 0, len(mymap))
1赞 Atila Romero 8/8/2018
这个答案比使用 len(mymap) 更安全,如果其他人在复制时更改了地图。
0赞 Petr 9/7/2021
@AtilaRomero这是真的吗?我假设在这种情况下,数据可能完全损坏,或者这是在 golang 的某个地方指定的?
1赞 Matias Barrios 11/9/2021
@Petr我认为这应该令人恐慌。不应有两个例程在同一张地图上工作。这就是同步。映射应用于互斥互斥,或将映射与互斥锁一起使用
0赞 Jeff Learman 6/25/2022
如果启用并发访问,则会导致数据争用错误。绝对是要避免的事情。
307赞 peterSO 1/26/2014 #2

例如

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

为了在 Go 中提高效率,尽量减少内存分配非常重要。

评论

46赞 Vinay Pai 1/9/2015
设置实际大小而不是容量并完全避免追加会稍微好一些。有关详细信息,请参阅我的回答。
3赞 Melllvar 7/26/2017
请注意,如果 不是局部变量(因此会增长/收缩),这是唯一正确的解决方案 - 它确保如果 的大小在初始化和循环之间发生变化,则不会出现任何越界问题。mymapmymapkeysfor
18赞 Vinay Pai 7/27/2017
映射在并发访问下是不安全的,如果另一个 goroutine 可能会更改映射,则这两种解决方案都是不可接受的。
6赞 Vinay Pai 8/9/2018
@darethas这是一个常见的误解。自 1.6 以来,race 检测器将标记此用法。从发行说明中可以看出:“与往常一样,如果一个 goroutine 正在写入地图,则其他 goroutine 不应同时读取或写入地图。如果运行时检测到这种情况,它会打印诊断并导致程序崩溃。golang.org/doc/go1.6#runtime
1赞 xuiqzy 9/9/2020
@darethas为什么这有时是安全的,有时是问题呢?从 Vinay Pais 的评论来看,这似乎是一个坏主意?
557赞 Vinay Pai 1/9/2015 #3

这是一个老问题,但这是我的两分钱。PeterSO的回答稍微简洁一些,但效率略低一些。你已经知道它会有多大,所以你甚至不需要使用 append:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

在大多数情况下,它可能不会有太大的区别,但它不会有更多的工作,在我的测试中(使用具有 1,000,000 个随机键的映射,然后用每种方法生成键数组十次),直接分配数组成员比使用 append 快约 20%。int64

尽管设置容量可以消除重新分配,但追加仍然需要执行额外的工作来检查每个追加是否已达到容量。

评论

79赞 Emmaly 3/8/2015
这看起来与 OP 的代码完全相同。我同意这是更好的方法,但我很好奇我是否错过了这个答案的代码和 OP 的代码之间的区别。
8赞 Vinay Pai 3/10/2015
好点子,我不知何故看了其他答案,错过了我的答案与 OP 完全相同。 哦,好吧,至少我们现在知道使用不必要的附加:)的惩罚是什么
7赞 mvndaai 4/30/2016
为什么不使用范围为 .这样你就不需要 i++?for i, k := range mymap{
51赞 Vinay Pai 5/3/2016
也许我在这里遗漏了一些东西,但如果你这样做了,那么将是键,并且将是与地图中这些键相对应的值。这实际上不会帮助您填充一部分密钥。i, k := range mymapik
5赞 Vinay Pai 10/9/2018
@Alaska,如果您担心分配一个临时计数器变量的成本,但认为函数调用将占用更少的内存,那么您应该了解调用函数时实际发生的情况。提示:这不是一个免费做事的魔法咒语。如果您认为当前接受的答案在并发访问下是安全的,则还需要回到基础:blog.golang.org/go-maps-in-action#TOC_6
125赞 Denis Kreshikhin 4/3/2016 #4

您还可以从包“reflect”中获取一个类型为 by struct 方法的键数组:[]ValueMapKeysValue

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}

评论

1赞 Atila Romero 8/9/2018
如果有机会并发地图访问,我认为这是一个很好的方法:如果地图在循环期间增长,这不会惊慌失措。关于性能,我不太确定,但我怀疑它的性能优于追加解决方案。
0赞 Denis Kreshikhin 8/17/2018
@AtilaRomero 不确定此解决方案是否有任何优点,但是当出于任何目的使用反射时,这更有用,因为它允许直接将键作为值类型化。
44赞 Doron Behar 5/29/2019
有没有办法将其转换为?[]string
1赞 gtato 10/8/2020
@AtilaRomero我刚刚测试了它,但当列表增长时,它仍然会恐慌。
1赞 Jeremy Giaco 6/29/2021
这就是我一直在寻找的答案。我正在尝试合并两个不同的“实体”结构,它们都包含一个 map[string][]*Foo。因此,对于第一个结构,我需要识别键,这样我就可以在第二个结构中逐个键查找它,而不是遍历每个映射值以查看我是否要合并到其中的键。谢谢!
24赞 Nico Villanueva 2/11/2019 #5

我对其他回复中描述的三种方法做了一个粗略的基准测试。

显然,在拉动密钥之前预先分配切片比 ing 快,但令人惊讶的是,该方法明显慢于后者:appendreflect.ValueOf(m).MapKeys()

❯ go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

代码如下:https://play.golang.org/p/Z8O6a2jyfTH(在操场上运行它会中止,声称它需要太长时间,所以,好吧,在本地运行它。

评论

5赞 keithbhunter 4/1/2019
在您的函数中,您可以使用 设置数组的容量,这极大地改变了该函数的性能。keysAppendkeysmake([]uint64, 0, len(m))
0赞 Enver Bisevac 3/24/2021
@keithbhunter我同意,几乎没有区别。
0赞 rdnobrega 4/30/2021
@keithbhunter 调用 make([]int, len(m)) 和 make([]int, 0, len(m) 实际上是一回事:在内存中预分配数组,这将完全违背测试的目的。
2赞 Lalit Sharma 7/10/2019 #6

访问 https://play.golang.org/p/dx6PTtuBXQW

package main

import (
    "fmt"
    "sort"
)

func main() {
    mapEg := map[string]string{"c":"a","a":"c","b":"b"}
    keys := make([]string, 0, len(mapEg))
    for k := range mapEg {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    fmt.Println(keys)
}
90赞 blackgreen 11/9/2021 #7

Go 现在有泛型。您可以使用地图获取任何地图的密钥。键

用法示例:

    intMap := map[int]int{1: 1, 2: 2}
    intKeys := maps.Keys(intMap)
    // intKeys is []int
    fmt.Println(intKeys)

    strMap := map[string]int{"alpha": 1, "bravo": 2}
    strKeys := maps.Keys(strMap)
    // strKeys is []string
    fmt.Println(strKeys)

maps包位于 中。这是实验性的,超出了 Go 兼容性保证。他们的目标是在未来将其移动到 Go 1.19 的 std 库中。golang.org/x/exp/maps

游乐场: https://go.dev/play/p/fkm9PrJYTly

对于那些不喜欢导入 exp 包的人,这里是源代码(最初由 Ian Lance Taylor 编写),正如你所看到的,它非常简单:

// Keys returns the keys of the map m.
// The keys will be an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

注意:在 Go 1.21 中,软件包的一部分已移至标准库中,但未移至 .有关详细信息,请参阅 Go 问题图:删除 Go 1.21 tl 的键和值;dr 该方法最终可能会具有不同的签名。因此,在 Go 1.21 中,这里提供的解决方案(使用或复制源代码)仍然适用。mapsmaps.KeysKeysx/exp/maps

评论

0赞 Marko 7/18/2022
in 是什么意思?~~map[K]V
3赞 blackgreen 7/18/2022
@Marko请看 Go 中新的波浪号标记 ~ 是什么意思?
3赞 Eric 7/7/2022 #8

answer 的通用版本 (go 1.18+)。Vinay Pai

// MapKeysToSlice extract keys of map as slice,
func MapKeysToSlice[K comparable, V any](m map[K]V) []K {
    keys := make([]K, len(m))

    i := 0
    for k := range m {
        keys[i] = k
        i++
    }
    return keys
}
1赞 Alex Kosh 7/24/2022 #9

有一个很酷的库叫做 lo

基于 Go 1.18+ 泛型的 Lodash 风格的 Go 库(map、filter、contains、find...

有了这个库,你可以做许多方便的操作,如地图、过滤、减少等等。还有一些类型的帮助程序map

钥匙

创建映射键的数组。

keys := lo.Keys[string, int](map[string]int{"foo": 1, "bar": 2})
// []string{"bar", "foo"}

创建映射值的数组。

values := lo.Values[string, int](map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
6赞 Saurabh 6/15/2023 #10

假设是 类型,您可以使用标准库中的实验性 maps 包获取键和值:mapmap[int]string

package main

import (
    "fmt"
    "golang.org/x/exp/maps"
)


func main() {
    mymap := map[int]string{1: "foo", 2: "bar", 3: "biz"}

    fmt.Println(maps.Keys(mymap))
    fmt.Println(maps.Values(mymap))
}

输出:

[2 3 1]
[bar biz foo]

评论

1赞 Evan Byrne 6/29/2023
应该注意的是,两张地图的结果顺序。钥匙和地图。值是不确定的,因此不能假定一个结果的顺序与另一个匹配。