在 Go 中创建2D 切片的简洁方法是什么?

我通过学习 围棋之旅来学习围棋。其中一个练习要求我创建一个包含 uint8dy行和 dx列的2D 切片。我目前行之有效的方法是:

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 维)切片?

159446 次浏览

没有比这更简洁的方法了,你所做的是“正确的”方法; 因为切片总是一维的,但是可以组合来构造更高维的对象。有关更多细节,请参见此问题: 去: 如何是二维数组的内存表示

可以简化的一件事是使用 for range结构:

a := make([][]uint8, dy)
for i := range a {
a[i] = make([]uint8, dx)
}

还要注意的是,如果你用 复合文字初始化你的切片,你可以免费得到它,例如:

a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

是的,这有它的局限性,因为看起来你必须枚举所有的元素; 但是有一些技巧,即你不必枚举所有的值,只有那些不是片元素类型的 零值的值。有关这方面的详细信息,请参阅 Golang 数组初始化中的键项

例如,如果您想要一个片,其中前10个元素为零,然后遵循 12,它可以像这样创建:

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

还要注意,如果你使用 数组而不是 切片,它可以很容易地创建:

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]]

对于数组,您不必迭代“外部”数组并初始化“内部”数组,因为数组不是描述符,而是值。更多细节见博客文章 数组、切片(和字符串) : “ append”的机制

试试 去游乐场上的例子。

使用切片创建矩阵有两种方法。让我们看看它们之间的区别。

第一种方法:

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 例程的例子,它们可能导致这种情况:

  1. 例程 # 0运行 make([][]int, n)matrix获取分配的内存,从0x000到0x07F 获取一块内存。
  2. 然后,它启动循环并执行第一行 make([]int, m),从0x080到0x0FF。
  3. 在第二次迭代中,它被调度程序抢占。
  4. 调度程序将处理器交给例程 # 1,然后它开始运行。这个函数也使用 make(为了它自己的目的) ,从0x100到0x17F (就在例程 # 0的第一行旁边)。
  5. 过了一会儿,它被抢占,例程 # 0又开始运行。
  6. 它执行与第二个循环迭代相对应的 make([]int, m),并从0x180获取第二行的0x1FF。此时,我们已经有了两个分隔行。

在第二种方法中,例程执行 make([]int, n*m)以获得分配在一个切片中的所有矩阵,从而确保连续性。之后,需要一个循环来更新矩阵指针到对应于每一行的子切片。

您可以使用上面在 去游乐场中显示的代码来查看通过使用这两种方法分配的内存的差异。注意,我使用 runtime.Gosched()仅仅是为了生成处理器并强制调度程序切换到另一个例程。

用哪一个?想象一下第一个方法的最坏情况,即每一行在内存中不在另一行的旁边。然后,如果您的程序遍历矩阵元素(读或写它们) ,那么与第二种方法相比,由于数据局部性较差,可能会有更多的缓存丢失(因此延迟较高)。另一方面,使用第二种方法,由于内存碎片(内存分散在整个内存中) ,可能不可能为矩阵分配一块内存,即使理论上可能有足够的空闲内存。

因此,除非存在大量内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来获得数据本地性的优势。

这里有一个简洁的方法:

value := [][]string{}{[]string{}{"A1","A2"}, []string{}{"B1", "B2"}}

附注: 您可以将“ string”更改为您在切片中使用的元素类型。

有了 Go 1.18,你就得到了 非专利药

下面是一个使用泛型的函数,它允许为任何单元格类型创建一个2D 切片。

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)

你可以玩 游乐场的密码