Scala 的 Vector 是如何工作的?

Posted

技术标签:

【中文标题】Scala 的 Vector 是如何工作的?【英文标题】:How does Scala's Vector work? 【发布时间】:2013-12-16 13:59:51 【问题描述】:

我阅读了 this page 关于 Scala 集合的时间复杂度的文章。正如它所说,Vector 对于所有操作的复杂性是eC

这让我想知道Vector 是什么。我读了document,上面写着:

由于向量在快速随机选择和快速随机函数更新之间取得了很好的平衡,因此它们目前是 不可变索引序列的默认实现。它由 分支因子为 32 的小端位图矢量树。 局部性非常好,但不连续,这对非常有好处 大序列。

与 Scala 的其他所有内容一样,它非常模糊。 Vector 究竟是如何工作的?

【问题讨论】:

继续你的陈述就像关于 Scala 的其他一切一样,你关于 Scala 向量的问题也很模糊。你到底想听到什么关于矢量的信息?高级描述非常好,对于低级细节,查看sources 是有意义的。 再一次,如果他有兴趣知道表面之下是哪种数据结构,那么源代码将无济于事(鉴于稀疏的 cmets),因为您基本上应该已经知道算法和结构来理解它。 实际上,这非常具有描述性。实现分支因子为 32 的位映射向量树的方法并不多。也许可以添加“变异”元素创建从元素到根的直线数组的副本,但真的没有那么多的源代码(当然是可用的)。 当前文档 (2.13.6) 说,“向量由宽度为 32 的基数平衡手指树实现。” 【参考方案1】:

这里的关键字是Trie。 Vector 被实现为Trie 数据结构。 见http://en.wikipedia.org/wiki/Trie。

更准确地说,它是一个“位映射向量树”。我刚刚在这里找到了对结构的足够简洁的描述(以及一个实现 - 显然是在 Rust 中):

https://bitbucket.org/astrieanna/bitmapped-vector-trie

最相关的摘录是:

Bitmapped Vector Trie 基本上是一棵 32 棵树。级别 1 是一个大小为 32 的数组,无论数据类型如何。 Level 2 是一个由 32 个 Level 1 组成的数组。以此类推,直到:Level 7 是一个由 2 个 Level 6 组成的数组。

更新:回复赖玉轩关于复杂性的评论:

我将不得不假设您在这里的意思是“深度”:-D。 “eC”的图例说“该操作实际上需要恒定的时间,但这可能取决于一些假设,例如向量的最大长度或散列键的分布。”。

如果您愿意考虑最坏的情况,并且考虑到向量的最大大小有一个上限,那么确实可以说复杂度是恒定的。 假设我们认为最大大小为 2^32,那么这意味着最坏的情况是最多 7 次操作,无论如何。 话又说回来,我们总是可以考虑任何类型的集合的最坏情况,找到一个上限并说这是恒定的复杂度,但对于一个示例列表,这意味着一个 40 亿的常数,这不太实际。

但 Vector 正好相反,7 次操作已经超出了实用性,这就是我们可以考虑到其复杂性常数的方式在实践中

另一种看待这个问题的方式:我们不是在谈论 log(2,N),而是 log(32,N)。如果您尝试绘制它,您会发现它实际上是一条水平线。所以务实地说,随着集合的增长,您将永远无法看到处理时间的大幅增加。 是的,这仍然不是真正恒定的(这就是为什么它被标记为“eC”而不仅仅是“C”),并且您将能够看到短向量周围的差异(但同样,差异非常小,因为数字的业务增长非常缓慢)。

【讨论】:

如果只是尝试,死亡是log(32,N),所以复杂度应该是O(logN)而不是eC,不是吗? @LaiYu-Hsuan 新docs中描述了“有效恒定时间”的含义 @RégisJean-Gilles(AFAICT,*** 不可能支持将 cmets 定向到重音名称。)你能澄清一下AFAII this means that given that we have ...吗? 抱歉,这只是编辑的遗留问题。我删除了这句话。【参考方案2】:

“Trie”的其他答案很好。但作为一个近似值,只是为了快速理解:

Vector 内部使用树结构 - 不是二叉树,而是 32 叉树 每个“32 路节点”使用 Array[32] 并且可以存储 要么 0-32 对子节点的引用 0-32 条数据 树的结构以某种方式平衡 - 它有“n”级深,但级别 1 到 n-1 是“仅索引级别”(100% 子引用;无数据)并且级别 n 包含所有数据(100% 数据;无子引用)。因此,如果数据元素的数量为“d”,则 n = log-base-32(d) 向上舍入

为什么会这样?很简单:为了性能。

不是为每个单独的数据元素分配数千/数百万/数十亿的内存,而是以 32 个元素块分配内存。结构非常浅,而不是步行数英里深才能找到您的数据 - 它是一棵非常宽且短的树。例如。 5 层深度可以包含 32^5 个数据元素(对于 4 字节元素 = 132GB,即相当大),并且每个数据访问将从根开始查找并遍历 5 个节点(而大数组将使用单个数据访问)。向量不会主动为所有级别 n(数据)分配内存,它会根据需要分配 32 个元素块。它提供类似于大型数组的读取性能,同时具有类似于二叉树的功能特性(功率、灵活性和内存效率)。

:)

【讨论】:

您能否描述一下它是如何同时实现“eC”附加/前置的?【参考方案3】:

这些可能对你来说很有趣:

Ideal Hash Trees 菲尔·巴格威尔。 Implementing Persistent Vectors in Scala - Daniel Spiewak More Persistent Vectors: Performance Analysis - Daniel Spiewak Persistent data structures in Scala

【讨论】:

以上是关于Scala 的 Vector 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

spark实现item2Vec算法-附scala代码

spark实现item2Vec算法-附scala代码

Vector<Vec3b> 与 OpenCV 中 Vector<int> 的区别

使用 std::vector,为啥 &vec[0] 是未定义的行为,但 vec.data() 是安全的?

向量的指针向量

mfc c++ 初学,vector怎么最简单地使用二维数组vector<int,int> vec;