如何从映射中获取一个值片?

如果我有一个地图 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 包中是否有一个函数,或者这是唯一的方法?

150955 次浏览

不幸的是,没有。没有固定的方法来做到这一点。

顺便说一句,您可以在片创建中省略容量参数:

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

容量意味着与这里的长度相同。

作为 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的元素数量初始化的。这样,每当片 v的容量用完时,append就不需要分配内存。

您也可以在没有容量值的情况下 make切片,并让 append为自己分配内存。

据我目前所知,go 没有一个 way 方法可以将字符串/字节连接到结果字符串中,而不需要至少创建/两个/副本。

因为所有的字符串值都是 const,所以当前你必须增长一个[]字节,然后你必须使用内建的字符串来创建一个“受保护的”字符串对象,它将为此复制缓冲区,因为某个地方可能有一个对支持[]字节的地址的引用。

如果一个[]字节是合适的,那么您可以获得一个非常轻微的领先字节。通过进行一次分配和做复制调用你自己加入函数。

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)
}

截至1.18,这是最好的办法: Https://stackoverflow.com/a/71635953/130427

1.18前

你可以使用这个 maps软件包:

go get https://github.com/drgrib/maps

那你只需要打电话

values := maps.GetValuesIntString(m)

对于常见的 map组合,它是类型安全的。使用同一个包中的 mapper工具,您可以为任何其他类型的 map使用 generate其他类型安全的函数。

充分披露: 我是这个软件包的创造者。我创建它是因为我发现自己重复为 map重写这些函数。

不一定更好,但更干净的方法是定义两个 切片长度和容量,如 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分18秒

您可以从 golang.org/x/exp包中使用 maps.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包中复制粘贴的:

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
}