从地图中获取一片钥匙

Sas*_*dhi 195 go

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

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

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}
Run Code Online (Sandbox Code Playgroud)

Vin*_*Pai 328

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

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

i := 0
for k := range mymap {
    keys[i] = k
    i++
}
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,它可能不会产生太大的影响,但它不是更多的工作,并且在我的测试中(使用带有1,000,000个随机int64密钥的地图,然后使用每种方法生成密钥数组十次),它是关于分配成员直接比使用追加快20%.

尽管设置容量会消除重新分配,但追加仍然需要做额外的工作来检查每个附加是否达到了容量.

  • 这看起来与OP的代码完全相同.我同意这是更好的方法,但我很好奇,如果我错过了这个答案的代码和OP的代码之间的区别. (38认同)
  • 也许我在这里遗漏了一些东西,但是如果你做了`i,k:= range mymap`,那么`i`将是键,而`k`将是与地图中的那些键相对应的值.这实际上不会帮助您填充一小部分密钥. (27认同)
  • 好点,我不知怎的看了其他的答案,错过了我的答案与OP完全相同.哦,好吧,至少我们现在知道不必要地使用追加的惩罚是什么:) (4认同)
  • 你为什么不使用范围索引,`for i,k:= range mymap {`.那样你不需要i ++? (4认同)
  • @Alaska如果你担心分配一个临时计数器变量的成本,但是认为一个函数调用会占用更少的内存,你应该教会自己调用函数时实际发生的事情.提示:这不是一个神奇的咒语,可以免费做事.如果您认为当前接受的答案在并发访问下是安全的,您还需要回到基础知识:https://blog.golang.org/go-maps-in-action#TOC_6. (3认同)
  • 无论什么漂浮你的船.我个人宁愿编写两行但更高效的代码. (2认同)

pet*_*rSO 176

例如,

package main

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

为了在Go中高效,最小化内存分配很重要.

  • 设置实际大小而不是容量稍微好一点,避免完全追加.详情请见我的回答. (23认同)
  • 并发访问下的映射是不安全的,如果另一个goroutine可能会更改映射,则这两种解决方案都不可接受. (9认同)
  • @darethas 这是一个常见的误解。从 1.6 开始,竞态检测器将标记此用法。来自发行说明:“与往常一样,如果一个 goroutine 正在写入地图,则没有其他 goroutine 应该同时读取或写入地图。如果运行时检测到这种情况,它会打印诊断信息并使程序崩溃。” https://golang.org/doc/go1.6#runtime (4认同)
  • 请注意,如果`mymap`不是局部变量(因此需要增长/缩小),这是唯一合适的解决方案 - 它确保``mymap`的大小在`keys`和`的初始化之间发生变化. for循环,不会有任何越界问题. (3认同)

bla*_*een 106

Go 现在有了泛型。您可以使用 获取任何地图的钥匙maps.Keys。

用法示例:

    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)
Run Code Online (Sandbox Code Playgroud)

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

游乐场: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
}
Run Code Online (Sandbox Code Playgroud)

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

  • @Marko 请参阅 [Go 中新波浪线标记 ~ 的含义是什么?](/sf/ask/4962176831/ ) (3认同)

Den*_*hin 70

您还可以[]Value通过包"reflect"中MapKeys的struct 方法获取类型的键数组Value:

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]
}
Run Code Online (Sandbox Code Playgroud)

  • 有没有办法将其转换为`[] string`? (3认同)

rig*_*old 11

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

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}
Run Code Online (Sandbox Code Playgroud)

除此之外,你运气不好 - Go不是一个非常富有表现力的语言.

  • 它的效率低于原始值 - append将进行多次分配以增长底层数组,并且必须在每次调用时更新切片长度.说`keys = make([] int,0,len(mymap))`将摆脱分配,但我预计它仍然会更慢. (9认同)

Nic*_*eva 7

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

显然,在拉动键之前预先分配片比appending 快,但是令人惊讶的reflect.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
Run Code Online (Sandbox Code Playgroud)

这是代码:https : //play.golang.org/p/Z8O6a2jyfTH (在操场上运行它会中止宣称它花费的时间太长,所以,可以在本地运行它。)

  • 在您的keysAppend函数中,可以使用make([] uint64,0,len(m))来设置keys数组的容量,这对我来说大大改变了该函数的性能。 (2认同)

Sau*_*abh 6

假设map是类型map[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))
}
Run Code Online (Sandbox Code Playgroud)

输出:

[2 3 1]
[bar biz foo]
Run Code Online (Sandbox Code Playgroud)