Kotlin - 确保收集操作在开始下一个之前完成

Kotlin - make sure a collection operation finishes before starting the next one

如果编写如下代码:

fun main(args: Array<String>) {
    val letters = listOf("a", "b", "c", "d")
    letters.map { print(it); it }.forEach { print(it) }
}

我得到这个输出:abcdabcd

但是,如果我将其更改为序列:

fun main(args: Array<String>) {
    val letters = listOf("a", "b", "c", "d")
    letters.asSequence().map { print(it); it }.forEach { print(it) }
}

我得到以下信息:aabbccdd

Kotlin 的序列似乎不能保证 "group operation"(例如 mapforEach)会在另一个开始之前完成,但是当我们将这些操作应用于集合时,有保证操作一次完成。

我们如何保证 "group operation" 例如 mapforEach 在开始执行下一个链接 "group operation"?

How can we guarantee that a "group operation" such as map or forEach will be applied to all the elements in a collection or a sequence before starting to execute the next chained "group operation"?

行为取决于输入是 Collection 还是 Sequence

  • 集合会将链中的单个操作应用于集合中的每个元素,然后再应用链中的下一个操作。这就是为什么您看到 "abcd" 一次然后又看到 "abcd" 的原因。
  • 序列会将链中的每个操作应用于序列中的每个元素,然后再将所有操作应用于序列中的下一个元素。这就是为什么您会看到 "aa",然后是 "bb",等等

主要要理解的是 Kotlin 序列是 惰性的。每当您转换 Sequence(例如通过 map 或其他函数)时,该转换也会 延迟应用。只有当您实际评估序列中的元素时(在您的情况下,这是由 forEach 完成的),才会应用转换。

如果您需要 SequenceCollection 行为,您必须先将序列转换为集合。您可以调用 sequence.toList() 或以其他方式对其进行转换,但是无法从开箱即用的 Sequence 中获得 "eager" 行为。


让我们来看看这两种不同的场景。

lettersList<String>

列表并不懒惰。为了 map 一个 List 到另一个,我们必须遍历整个输入列表,转换每个元素,并构建输出列表。考虑到这一点...

第一个操作 map { print(it); it } 从输入中读取每个值,打印它,并创建一个新列表。它 returns 一个包含 "a", "b", "c", "d" 的列表并且程序打印了 "abcd".

第二个操作 forEach { print(it) } 遍历列表并打印每个元素。此操作完成后,程序再次打印 "abcd",总输出为 "abcdabcd".

lettersSequence<String>

序列是惰性的。为了map一个Sequence到另一个,程序保留了转换的方法,等到别人确实需要一个值,才进行转换。所以...

第一个操作创建一个新的 TransformingSequence,它包含原始序列和 print(it); return it 转换器。它 returns 这个新序列和程序还没有打印任何东西。

第二个操作是"terminal"(即它实际评估序列中的每个元素)。它遍历序列并打印每个元素。但请记住:我们迭代的序列是一个 TransformingSequence 本身打印每个元素!因此 forEach 调用中的第一个 print 操作打印 "aa",第二个打印 "bb",依此类推,总输出 "aabbccdd".