为什么列表在 Go 中很少使用?

有没有办法在 Go 中创建一个没有硬编码数组大小的数组/片? 为什么忽略 List?

在我使用过的所有语言中: Delphi,C # ,C + + ,Python-List 非常重要,因为它们可以动态调整大小,而不是数组。

在 Golang,确实有一个 list.Liststruct,但我很少看到有关它的文档——无论是在 以身作则还是我手头的三本围棋书籍—— Summerfield、 Chisnal 和 Balbaert ——它们都在数组和切片上花费了大量时间,然后跳到了地图上。在源代码示例中,我还发现很少或根本没有使用 list.List

与 Python 不同,Range似乎不支持 List-big 的缺点 IMO。我错过了什么吗?

切片是可爱的,但它们仍然需要基于具有硬编码大小的数组。这就需要李斯特了。

103584 次浏览

我认为这是因为没有太多关于他们的说法,因为 container/list包是相当不言自明的 一次你吸收了什么是处理通用数据的主要围棋习语。

在 Delphi (没有泛型)或 C 中,您将在列表中存储指针或 TObject,然后在从列表中获取时将它们强制转换回它们的实际类型。在 C + + 中,STL 列表是模板,因此通过类型参数化,而在 C # 中(现在)列表是通用的。

在 Go 中,container/list存储 interface{}类型的值,这是一种特殊类型,能够表示任何其他(实)类型的值ーー通过存储一对指针: 一个指向所包含值的类型 info,一个指向值的指针(或者直接指向值,如果它的大小不超过指针的大小)。因此,当您想要向列表中添加一个元素时,您只需要将其作为 interface{}类型的函数参数接受 coo 任何类型的值即可。但是,当您从列表中提取值时,以及如何处理它们的实际类型时,您必须对它们执行 类型声明型式开关型式开关型式开关操作ーー这两种方法实质上是以不同的方式执行相同的操作。

下面是一个来自 给你的例子:

package main


import ("fmt" ; "container/list")


func main() {
var x list.List
x.PushBack(1)
x.PushBack(2)
x.PushBack(3)


for e := x.Front(); e != nil; e=e.Next() {
fmt.Println(e.Value.(int))
}
}

在这里,我们使用 e.Value()获得一个元素的值,然后将它作为原始插入值的类型 int进行类型断言。

你可以在“有效行动”或任何其他介绍书中了解类型断言和类型转换。container/list包的文档总结了支持的所有方法列表。

注意,Go 切片可以通过 append()内置函数进行扩展。虽然有时候这需要复制一个备份数组,但是这种情况不会每次都发生,因为 Go 会使新数组的容量超过报告的长度。这意味着可以在没有其他数据副本的情况下完成后续追加操作。

虽然最终获得的数据副本比使用链表实现的等价代码要多,但是不需要分配列表中的元素,也不需要更新 Next指针。对于许多应用来说,基于数组的实现提供了更好或者足够好的性能,因此这就是语言所强调的。有趣的是,Python 的标准 list类型也支持数组,并且在附加值时具有类似的性能特征。

That said, there are cases where linked lists are a better choice (e.g. when you need to insert or remove elements from the start/middle of a long list), and that is why a standard library implementation is provided. I guess they didn't add any special language features to work with them because these cases are less common than those where slices are used.

当你想到一个列表的时候,在 Go 中使用一个切片。切片是动态调整大小的。它们下面是一个可以改变大小的连续内存片。

他们是非常灵活的,因为你会看到,如果你读 SliceTricks 维基页面

以下是一段摘录:-

收到

b = make([]T, len(a))
copy(b, a) // or b = append([]T(nil), a...)

a = append(a[:i], a[j:]...)

删除

a = append(a[:i], a[i+1:]...) // or a = a[:i+copy(a[i:], a[i+1:])]

删除而不保留顺序

a[i], a = a[len(a)-1], a[:len(a)-1]

爸爸

x, a = a[len(a)-1], a[:len(a)-1]

用力

a = append(a, x)

更新 : 这里有一个来自 go 团队本身的到 关于切片的博客文章的链接,它很好地解释了切片和数组以及切片内部之间的关系。

几个月前我刚开始调查 Go 的时候问过这个问题。从那时起,我每天都在阅读围棋和围棋编程。

因为我没有得到这个问题的明确答案(尽管我已经接受了一个答案) ,所以现在我要根据我所学到的知识,自从我问了这个问题之后,我自己来回答它:

Is there a way to create an array /slice in Go without a hard coded 数组大小?

是的。切片不需要从以下位置到 slice的硬编码数组:

var sl []int = make([]int, len, cap)

这段代码分配片 sl,大小为 len,容量为 cap-lencap是可以在运行时分配的变量。

为什么忽略 list.List

list.List在围棋中似乎没有得到多少关注的主要原因是:

  • 正如“尼克•克雷格-伍德”的回答所解释的那样,确实存在 实际上,没有什么事情可以用无法完成的列表来完成 用切片,通常更有效率,用清洁剂,更多 elegant syntax. For example the range construct:

     for i := range sl {
    sl[i] = i
    }
    

    不能与 list 一起使用-循环需要 C 样式 在许多情况下,C + + 集合样式的语法必须与列表一起使用: push_back等。

  • 也许更重要的是,list.List不是强类型的——它与 Python 的列表和字典非常相似,后者允许在集合中混合各种类型。这似乎与事实相反 to the Go approach to things. Go is a very strongly typed language - for example, implicit type conversions never allowed in Go, even an upCast from int to int64 must be 但是,List 的所有方法都采用空接口- 什么都行。

    我放弃 Python 转而选择 Go 的原因之一是 在 Python 的类型系统中的这种弱点,尽管 Python 声称自己是“强类型的”(事实上并非如此) 是一种“杂种”,诞生于 C + + 的 vector<T>和 Python 的 List(),而且在 Go 本身中可能有点格格不入。

如果在不久的将来,我们找到了这个名单,我一点也不会感到惊讶。在 Go 中不推荐使用 List,尽管它可能会继续使用,以适应那些 稀有的的情况,即使使用良好的设计实践,也可以通过包含各种类型的集合来最好地解决问题。或者,它可能是为 C 家族的开发人员提供一座“桥梁”,让他们在学习 Go,AFAIK 所特有的切片的细微差别之前对 Go 感到舒适。(在某些方面,切片似乎与 C + + 或 Delphi 中的流类相似,但并非完全相似。)

虽然我来自 Delphi/C + +/Python 背景,但在我最初接触 Go 的时候,我发现 list.List比 Go 的片段更熟悉,因为我对 Go 更熟悉了,所以我回过头来把所有的列表都改成了片段。我还没有发现任何 slice和/或 map不允许我这样做,因此我需要使用 list.List

发信人: https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ



It depends a lot on the number of elements in your lists,
whether a true list or a slice will be more efficient
when you need to do many deletions in the 'middle' of the list.


#1
The more elements, the less attractive a slice becomes.


#2
When the ordering of the elements isn't important,
it is most efficient to use a slice and
deleting an element by replacing it by the last element in the slice and
reslicing the slice to shrink the len by 1
(as explained in the SliceTricks wiki)

那么
用薄片
1. 如果列表中元素的顺序不重要,并且需要删除,只需
use List swap the element to delete with last element, & re-slice to (length-1)
当元素更多时(无论多少意味着多少)


There are ways to mitigate the deletion problem --
e.g. the swap trick you mentioned or
just marking the elements as logically deleted.
But it's impossible to mitigate the problem of slowness of walking linked lists.

那么
use slice
1. 如果你需要在穿越中加速

除非切片更新得太频繁(删除、随机添加元素) ,否则与链表相比,切片的内存连续性将提供出色的缓存命中率。

Scott Meyer 关于缓存重要性的演讲。 https://www.youtube.com/watch?v=WDIkqP4JbkE

list.List是作为一个双向链表实施的。在大多数情况下,如果不经常插入到列表的中间,基于数组的列表(C + + 中的向量,或 golang 中的切片)比链表更好。对于数组列表和链表,追加的摊销时间复杂度为 O (1) ,即使数组列表必须扩展容量并复制现有值。数组列表具有更快的随机访问、更小的内存占用,更重要的是对垃圾收集器友好,因为数据结构中没有指针。