在 Go 中如何从映射中获取值的切片?

问题描述 投票:0回答:5

如果我有地图

m
有没有比这更好的方法来获取值的切片
v

package main
import (
  "fmt"
)

func main() {
    m := make(map[int]string)

    m[1] = "a"
    m[2] = "b"
    m[3] = "c"
    m[4] = "d"

    // Can this be done better?
    v := make([]string, len(m), len(m))
    idx := 0
    for  _, value := range m {
       v[idx] = value
       idx++
    }

    fmt.Println(v)
 }

map
有内置功能吗? Go 包中是否有函数,或者这是执行此操作的唯一方法?

dictionary go slice
5个回答
95
投票

作为 jimt 帖子的补充:

您还可以使用

append
而不是显式地将值分配给它们的索引:

m := make(map[int]string)

m[1] = "a"
m[2] = "b"
m[3] = "c"
m[4] = "d"

v := make([]string, 0, len(m))

for  _, value := range m {
   v = append(v, value)
}

请注意,长度为零(尚无元素),但容量(分配的空间)是用

m
的元素数量初始化的。这样做是为了让
append
不需要在每次切片
v
的容量用完时分配内存。

您也可以

make
没有容量值的切片,并让
append
为自己分配内存。


81
投票

不幸的是,没有。没有内置的方法可以做到这一点。

作为旁注,您可以在切片创建中省略容量参数:

v := make([]string, len(m))

此处的容量默认与长度相同。


75
投票

Go 1.23(2024 年 8 月)

函数

maps.Values
将被移入标准库,但是从方法定义中可以看到,它不仅仅返回一个值的切片:它返回一个iterator。因此:

数值范围

迭代器是一个可以直接在range

子句中使用的函数
:

import ( "maps" ) func main() { m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} for v := range maps.Values(m) { // ... } }
游乐场:

https://go.dev/play/p/uA-NqBqan9I?v=gotip

将值收集到切片中

如果您需要将映射值放入切片中,则必须使用

slices.Collect

: 从迭代器中收集它们

import ( "fmt" "maps" "slices" ) func main() { m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} vals := slices.Collect(maps.Values(m)) fmt.Println(vals) // [a b c d] }
游乐场:

https://go.dev/play/p/oXLdlHHfpcN?v=gotip

这是高效的,因为迭代器是一个按需生成值的函数。不过,如果您觉得这很麻烦,您仍然可以像旧的

golang.org/x/exp/maps.Values

 那样编写自己的通用函数(下面的示例代码)。

转到1.18+

您可以使用

maps.Values 包中的

golang.org/x/exp

Values 返回地图 m 的值。这些值的顺序不确定。

func main() { m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"} v := maps.Values(m) fmt.Println(v) }
软件包

exp

包含实验代码。签名将来可能会也可能不会改变,并且可能会也可能不会升级到标准库。

如果您不想依赖实验包,您可以轻松地自己实现。事实上,以下代码片段是来自

exp/maps

的复制粘贴 
,最初由 Ian Lance Taylor 编写:

func Values[M ~map[K]V, K comparable, V any](m M) []V { r := make([]V, 0, len(m)) for _, v := range m { r = append(r, v) } return r }
    

12
投票
不一定更好,但更简洁的方法是定义

切片长度和容量,如txs := make([]Tx, 0, len(txMap))


// Defines the Slice capacity to match the Map elements count txs := make([]Tx, 0, len(txMap)) for _, tx := range txMap { txs = append(txs, tx) }
完整示例:

package main import ( "github.com/davecgh/go-spew/spew" ) type Tx struct { from string to string value uint64 } func main() { // Extra touch pre-defining the Map length to avoid reallocation txMap := make(map[string]Tx, 3) txMap["tx1"] = Tx{"andrej", "babayaga", 10} txMap["tx2"] = Tx{"andrej", "babayaga", 20} txMap["tx3"] = Tx{"andrej", "babayaga", 30} txSlice := getTXsAsSlice(txMap) spew.Dump(txSlice) } func getTXsAsSlice(txMap map[string]Tx) []Tx { // Defines the Slice capacity to match the Map elements count txs := make([]Tx, 0, len(txMap)) for _, tx := range txMap { txs = append(txs, tx) } return txs }
简单的解决方案,但有很多问题。阅读此博文了解更多详细信息:

https://web3.coach/golang-how-to-convert-map-to-slice- Three-gotchas


1
投票
据我目前所知,go 没有一种方法可以将字符串/字节连接到结果字符串中,而无需至少制作/两个/副本。

您当前必须增加一个 [] 字节,因为所有字符串值都是 const,然后您必须使用内置字符串来让语言创建一个“blessed”字符串对象,它将复制缓冲区,因为某个地方可能有一个对支持 [] 字节的地址的引用。

如果 []byte 合适,那么您可以获得比 bytes 稍稍领先的优势。通过进行一次分配并执行复制调用您自己来加入函数。

package main import ( "fmt" ) func main() { m := make(map[int]string) m[1] = "a" ; m[2] = "b" ; m[3] = "c" ; m[4] = "d" ip := 0 /* If the elements of m are not all of fixed length you must use a method like this; * in that case also consider: * bytes.Join() and/or * strings.Join() * They are likely preferable for maintainability over small performance change. for _, v := range m { ip += len(v) } */ ip = len(m) * 1 // length of elements in m r := make([]byte, ip, ip) ip = 0 for _, v := range m { ip += copy(r[ip:], v) } // r (return value) is currently a []byte, it mostly differs from 'string' // in that it can be grown and has a different default fmt method. fmt.Printf("%s\n", r) }
    
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.