我正在将一个项目从Scala 2.12.1迁移到2.13.6,发现SeqView#flatMap
现在返回一个View
,它没有distinct
方法。因此,我有一段代码不再编译:
val nodes = debts.view
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap
有一种愚蠢的方法可以解决这个问题,将视图转换为seq,然后再转换回视图:
val nodes = debts.view
.flatMap { case Debt(from, to, _) => List(from, to) }.toSeq.view
.distinct
.map(name => (name, new Node(name)))
.toMap
然而,这显然不太好,因为它迫使视图被收集,而且必须在类型之间来回切换也是非常不雅的。我找到了另一种修复方法,使用LazyList
:
val nodes = debts.to(LazyList)
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap
现在这就是我想要的,它的行为基本上就像一个Java流。当然,有些操作像distinct
一样使用O(n)
内存,但至少之后的所有操作都可以进行流式传输,而无需重建数据结构。
有了这一点,我开始思考为什么我们需要一个观点,因为它们的力量比以前小得多(即使我可以相信2.13已经解决了这个力量引入的其他一些问题(。我寻找答案,找到了一些暗示,但没有发现足够全面的东西。以下是我的研究:
- 2.13中的视图描述
- StackOverflow:List.view和LazyList之间有什么区别
- 外部网站上的另一个比较
可能是我,但即使在阅读了这些参考文献后,我也没有发现使用视图对大多数用例(如果不是全部的话(有明显的好处。有谁比我更开明?
View是最简单的延迟序列,几乎没有额外成本。在一般情况下,最好在默认情况下使用,以避免在处理大序列时分配中间结果。
可以多次遍历视图(使用foreach、foldLeft、toMap等(。每次遍历都会单独执行转换(map、flatMap、filter等(。因此,必须小心避免耗时的转换,或者只遍历视图一次。
迭代程序只能遍历一次。它类似于Java Streams或Python生成器。Iterator上的大多数转换方法都要求只使用返回的Iterator并丢弃原始对象。
它也像View一样快速,并支持更多操作,包括distinct。
LazyList基本上是一个真正严格的结构,可以动态自动扩展。LazyList会存储所有生成的元素。如果您有一个带有LazyList的val
,那么将为所有生成的元素分配内存。但是,如果您动态遍历它并且不存储在val
中,则垃圾收集器可以清理遍历的元素。
Scala2.12中的Stream比Views或Iterator慢很多。我不确定这是否适用于Scala2.13中的LazyList。
所以每个懒惰序列都有一些警告:
- 视图可以多次执行转换
- 迭代器只能使用一次
- LazyList可以为所有序列元素分配内存
在您的用例中,我相信Iterator是最合适的:
val nodes = debts.iterator
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap