在 Go 中创建 2D 切片的简洁方法是什么?
What is a concise way to create a 2D slice in Go?
我正在通过 A Tour of Go 学习围棋。其中一项练习要求我创建一个包含 dy
行和 dx
列的二维切片,其中包含 uint8
。我目前有效的方法是:
a:= make([][]uint8, dy) // initialize a slice of dy slices
for i:=0;i<dy;i++ {
a[i] = make([]uint8, dx) // initialize a slice of dx unit8 in each of dy slices
}
我认为遍历每个切片来初始化它太冗长了。如果切片有更多维度,代码将变得笨拙。有没有一种简洁的方法可以在 Go 中初始化 2D(或 n 维)切片?
没有更简洁的方法了,你做的就是"right"的方法;因为切片总是一维的,但可以组合起来构造更高维的对象。有关详细信息,请参阅此问题:.
您可以对其进行简化的一件事是使用 for range
构造:
a := make([][]uint8, dy)
for i := range a {
a[i] = make([]uint8, dx)
}
另请注意,如果您使用 composite literal 初始化您的切片,您会得到 "free",例如:
a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]
是的,这有其局限性,因为您似乎必须枚举所有元素;但是有一些技巧,即您不必枚举所有值,只枚举那些不是 zero values of the element type of the slice. For more details about this, see .
的值
例如,如果您想要一个前 10 个元素为零的切片,然后是 1
和 2
,可以这样创建:
b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]
另请注意,如果您使用 arrays instead of slices,可以非常轻松地创建它:
c := [5][5]uint8{}
fmt.Println(c)
输出为:
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
对于数组,您不必遍历 "outer" 数组并初始化 "inner" 数组,因为数组不是描述符而是值。有关详细信息,请参阅博客 post Arrays, slices (and strings): The mechanics of 'append'。
尝试 Go Playground 上的示例。
有两种方法可以使用切片创建矩阵。让我们来看看它们之间的区别。
第一种方法:
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, m)
}
第二种方法:
matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
matrix[i] = rows[i*m : (i+1)*m]
}
关于第一种方法,进行连续的 make
调用并不能确保您最终会得到一个连续的矩阵,因此您可能会在内存中分割矩阵。让我们考虑一个可能导致这种情况的两个 Go 例程的示例:
- 例程#0 运行
make([][]int, n)
来为matrix
分配内存,获得从0x000 到0x07F 的一块内存。
- 然后,它开始循环并执行第一行
make([]int, m)
,从 0x080 到 0x0FF。
- 在第二次迭代中,它被调度程序抢占。
- 调度程序将处理器分配给例程 #1 并启动 运行。这个也使用
make
(出于自己的目的)并从 0x100 获取到 0x17F(紧挨着例程 #0 的第一行)。
- 一段时间后,它被抢占,例程 #0 再次开始 运行。
- 它执行对应于第二个循环迭代的
make([]int, m)
,并从 0x180 到第二行的 0x1FF。此时,我们已经得到了两个分割行。
使用第二种方法,例程make([]int, n*m)
将所有矩阵分配到单个切片中,确保连续性。之后需要循环更新矩阵指针到每一行对应的子切片。
您可以使用上面显示的代码 Go Playground 来查看使用这两种方法分配的内存的差异。请注意,我使用 runtime.Gosched()
只是为了让出处理器并强制调度程序切换到另一个例程。
使用哪一个?想象一下第一种方法的最坏情况,即每一行在内存中都不是另一行的下一个。然后,如果您的程序遍历矩阵元素(以读取或写入它们),则由于数据局部性较差,与第二种方法相比,可能会有更多的缓存未命中(因此延迟更高)。另一方面,使用第二种方法可能无法为矩阵分配一块内存,因为内存碎片(块分布在整个内存中),即使理论上可能有足够的空闲内存.
因此,除非有很多内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来获得数据局部性的优势。
这里有一个可行的方法:
value := [][]string{}{[]string{}{"A1","A2"}, []string{}{"B1", "B2"}}
PS.: 您可以将“字符串”更改为您在切片中使用的元素类型。
使用 Go 1.18 你会得到 generics.
这是一个使用泛型允许为任何细胞类型创建二维切片的函数。
func Make2D[T any](n, m int) [][]T {
matrix := make([][]T, n)
rows := make([]T, n*m)
for i, startRow := 0, 0; i < n; i, startRow = i+1, startRow+m {
endRow := startRow + m
matrix[i] = rows[startRow:endRow:endRow]
}
return matrix
}
在您的工具箱中使用该函数,您的代码将变为:
a := Make2D[uint8](dy, dx)
我正在通过 A Tour of Go 学习围棋。其中一项练习要求我创建一个包含 dy
行和 dx
列的二维切片,其中包含 uint8
。我目前有效的方法是:
a:= make([][]uint8, dy) // initialize a slice of dy slices
for i:=0;i<dy;i++ {
a[i] = make([]uint8, dx) // initialize a slice of dx unit8 in each of dy slices
}
我认为遍历每个切片来初始化它太冗长了。如果切片有更多维度,代码将变得笨拙。有没有一种简洁的方法可以在 Go 中初始化 2D(或 n 维)切片?
没有更简洁的方法了,你做的就是"right"的方法;因为切片总是一维的,但可以组合起来构造更高维的对象。有关详细信息,请参阅此问题:
您可以对其进行简化的一件事是使用 for range
构造:
a := make([][]uint8, dy)
for i := range a {
a[i] = make([]uint8, dx)
}
另请注意,如果您使用 composite literal 初始化您的切片,您会得到 "free",例如:
a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]
是的,这有其局限性,因为您似乎必须枚举所有元素;但是有一些技巧,即您不必枚举所有值,只枚举那些不是 zero values of the element type of the slice. For more details about this, see
例如,如果您想要一个前 10 个元素为零的切片,然后是 1
和 2
,可以这样创建:
b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]
另请注意,如果您使用 arrays instead of slices,可以非常轻松地创建它:
c := [5][5]uint8{}
fmt.Println(c)
输出为:
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
对于数组,您不必遍历 "outer" 数组并初始化 "inner" 数组,因为数组不是描述符而是值。有关详细信息,请参阅博客 post Arrays, slices (and strings): The mechanics of 'append'。
尝试 Go Playground 上的示例。
有两种方法可以使用切片创建矩阵。让我们来看看它们之间的区别。
第一种方法:
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, m)
}
第二种方法:
matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
matrix[i] = rows[i*m : (i+1)*m]
}
关于第一种方法,进行连续的 make
调用并不能确保您最终会得到一个连续的矩阵,因此您可能会在内存中分割矩阵。让我们考虑一个可能导致这种情况的两个 Go 例程的示例:
- 例程#0 运行
make([][]int, n)
来为matrix
分配内存,获得从0x000 到0x07F 的一块内存。 - 然后,它开始循环并执行第一行
make([]int, m)
,从 0x080 到 0x0FF。 - 在第二次迭代中,它被调度程序抢占。
- 调度程序将处理器分配给例程 #1 并启动 运行。这个也使用
make
(出于自己的目的)并从 0x100 获取到 0x17F(紧挨着例程 #0 的第一行)。 - 一段时间后,它被抢占,例程 #0 再次开始 运行。
- 它执行对应于第二个循环迭代的
make([]int, m)
,并从 0x180 到第二行的 0x1FF。此时,我们已经得到了两个分割行。
使用第二种方法,例程make([]int, n*m)
将所有矩阵分配到单个切片中,确保连续性。之后需要循环更新矩阵指针到每一行对应的子切片。
您可以使用上面显示的代码 Go Playground 来查看使用这两种方法分配的内存的差异。请注意,我使用 runtime.Gosched()
只是为了让出处理器并强制调度程序切换到另一个例程。
使用哪一个?想象一下第一种方法的最坏情况,即每一行在内存中都不是另一行的下一个。然后,如果您的程序遍历矩阵元素(以读取或写入它们),则由于数据局部性较差,与第二种方法相比,可能会有更多的缓存未命中(因此延迟更高)。另一方面,使用第二种方法可能无法为矩阵分配一块内存,因为内存碎片(块分布在整个内存中),即使理论上可能有足够的空闲内存.
因此,除非有很多内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来获得数据局部性的优势。
这里有一个可行的方法:
value := [][]string{}{[]string{}{"A1","A2"}, []string{}{"B1", "B2"}}
PS.: 您可以将“字符串”更改为您在切片中使用的元素类型。
使用 Go 1.18 你会得到 generics.
这是一个使用泛型允许为任何细胞类型创建二维切片的函数。
func Make2D[T any](n, m int) [][]T {
matrix := make([][]T, n)
rows := make([]T, n*m)
for i, startRow := 0, 0; i < n; i, startRow = i+1, startRow+m {
endRow := startRow + m
matrix[i] = rows[startRow:endRow:endRow]
}
return matrix
}
在您的工具箱中使用该函数,您的代码将变为:
a := Make2D[uint8](dy, dx)