如果我有地图
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 包中是否有函数,或者这是执行此操作的唯一方法?
作为 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
为自己分配内存。
不幸的是,没有。没有内置的方法可以做到这一点。
作为旁注,您可以在切片创建中省略容量参数:
v := make([]string, len(m))
此处的容量默认与长度相同。
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
将值收集到切片中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
}
切片长度和容量,如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
您当前必须增加一个 [] 字节,因为所有字符串值都是 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)
}