在 Go 中创建 2D 切片的简洁方法是啥?
Posted
技术标签:
【中文标题】在 Go 中创建 2D 切片的简洁方法是啥?【英文标题】:What is a concise way to create a 2D slice in Go?在 Go 中创建 2D 切片的简洁方法是什么? 【发布时间】:2017-02-09 19:39:59 【问题描述】:我正在通过 A Tour of Go 学习 Go。那里的一个练习要求我创建一个包含uint8
的dy
行和dx
列的二维切片。我目前可行的方法是:
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 维)切片?
【问题讨论】:
【参考方案1】:没有更简洁的方式,你所做的是“正确”的方式;因为切片总是一维的,但可以组合起来构造更高维的对象。有关详细信息,请参阅此问题:Go: How is two dimensional array's memory representation。
您可以简化的一件事是使用for range
构造:
a := make([][]uint8, dy)
for i := range a
a[i] = make([]uint8, dx)
另请注意,如果您使用 composite literal 初始化切片,您将“免费”获得此信息,例如:
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 的值。有关这方面的更多详细信息,请参阅Keyed items in golang array initialization。
例如,如果您想要一个前 10 个元素为零的切片,然后跟随 1
和 2
,则可以这样创建:
b := []uint10: 1, 2
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]
另请注意,如果您使用arrays 而不是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]]
对于数组,您不必遍历“外部”数组并初始化“内部”数组,因为数组不是描述符而是值。有关详细信息,请参阅博客文章 Arrays, slices (and strings): The mechanics of 'append'。
试试Go Playground上的例子。
【讨论】:
由于使用数组可以简化代码,我想这样做。如何在结构中指定它?当我尝试将数组分配给我猜我告诉 Go 是一个切片时,我得到cannot use [5][2]string literal (type [5][2]string) as type [][]string in field value
。
自己想出来,并编辑了答案以添加信息。
@EricLindsey 虽然您的编辑很好,但我仍然会拒绝它,因为我不想仅仅因为初始化更容易而鼓励使用数组。在 Go 中,数组是次要的,切片是要走的路。详情见What is the fastest way to append one array to another in Go?数组也有自己的位置,详情见Why have arrays in Go?
很公平,但我相信这些信息仍然有价值。我试图用我的编辑来解释的是,如果您需要对象之间不同尺寸的灵活性,那么切片就是要走的路。另一方面,如果您的信息结构严格并且始终相同,那么数组不仅更容易初始化,而且效率更高。我该如何改进编辑?
@EricLindsey 我看到您进行了另一个已被其他人拒绝的编辑。在您的编辑中,您说使用数组来更快地访问元素。请注意,Go 优化了很多东西,但情况可能并非如此,切片可能同样快。详情请见Array vs Slice: accessing speed。【参考方案2】:
有两种方法可以使用切片来创建矩阵。让我们来看看它们之间的区别。
第一种方法:
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()
只是为了让处理器和强制调度程序切换到另一个例程。
使用哪一个?想象一下第一种方法的最坏情况,即每一行在内存中不是另一行的下一个。然后,如果您的程序遍历矩阵元素(以读取或写入它们),与第二种方法相比,由于数据局部性更差,可能会有更多的缓存未命中(因此延迟更高)。另一方面,使用第二种方法可能无法为矩阵分配一块内存,因为内存碎片(块遍布内存),即使理论上可能有足够的空闲内存。 .
因此,除非存在大量内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来利用数据局部性。
【讨论】:
golang.org/doc/effective_go.html#slices 展示了一种利用切片原生语法来执行连续内存技术的巧妙方法(例如,无需使用 (i+1)*m 之类的表达式显式计算切片边界)【参考方案3】:这里有一个简单的方法:
value := [][]string[]string"A1","A2", []string"B1", "B2"
PS.:您可以将“字符串”更改为您在切片中使用的元素类型。
【讨论】:
以上是关于在 Go 中创建 2D 切片的简洁方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章