使用 Yiedld 和 YieldAll 生成数据
使用 yield
,可以通过多种方式编写生成器函数来处理无限的数据集合。
当一个序列只是生成单个数据,仅使用 yield
即可满足需求。当遇到序列时,它会挂起序列,然后继续进行下一次迭代,以此类推。
以下是 SequenceYieldExample.kt 中提供的演示功能的工作示例:
fun main() {
// 1
val sequence = singleValueExample()
sequence.forEach {
println(it)
}
}
fun singleValueExample() = sequence {
// 2
println("Printing first value")
yield("Apple")
// 3
println("Printing second value")
yield("Orange")
// 4
println("Printing third value")
yield("Banana")
}
上述代码片段有几件事情:
- 你使用
singleValueExample()
创建了一个序列并迭代它打印每个子项。 - 在
singleValueExample()
中你首先打印了一条语句,然后yield("Apple")
。 - 加下来你同样生成了第二个数据 —— “Orange”。
- 最后是第三个数据 —— “Banana”。
执行上述代码,输出如下:
Printing first value
Apple
Printing second value
Orange
Printing third value
Banana
上述代码片段一次打印一个数据,然后挂起指定遇到下一个 yield
才会恢复。这是一个非常简单的过程,其中数据是一个接一个地生成的,yield
确保一次处理一个数据。实际上,可以通过 yield
不断产生更多的数据。
然而,这就引出了一个问题,如何使用一个范围数据生成序列?你应该不希望每当范围数据的序列中生成新值时都调用 yield
函数。是时候使用 yieldAll(elements:Iterable<T>)
函数了。此函数的签名如下:
public suspend fun yieldAll(elements: Iterable<T>)
为了验证它的行为,以下是 IteratorYieldAllExample.kt 中提供的功能演示代码片段:
fun main() {
// 1
val sequence = iterableExample()
sequence.forEach {
print("$it ")
}
}
fun iterableExample() = sequence {
// 2
yieldAll(1..5)
}
在上述代码中:
- 你使用
iterableExample()
创建了一个序列并迭代它打印每个子项。 - 你通过
yieldAll
生成了 [1..5] 一组整数,当每次生成一个整数时都会挂起。
执行上述代码片段,输出如下:
1 2 3 4 5
在序列是无限的时,Kotlin 标准库提供了另一个辅助函数,它是一个名为 yieldAll(sequence: Sequence<T>)
的重载函数,它将序列作为入参而不是迭代器。
下面是它在源码中的声明:
public suspend fun yieldAll(sequence: Sequence<T>) = yieldAll(sequence.iterator())
要演示其用法,请查看以下示例,该示例在 SequenceYieldAllExample.kt 中提供:
fun main() {
// 1
val sequence = sequenceExample().take(10)
sequence.forEach {
print("$it ")
}
}
fun sequenceExample() = sequence {
// 2
yieldAll(generateSequence(2) { it * 2 })
}
同样的,在上述代码中:
- 使用
sequenceExample()
创建了一个序列,然后迭代它打印每个子项。 - 使用
generateSequence()
生成一个无限的整数数字并传递每次生成的整数给yieldAll
函数。生成整数的方式是从数字 2 开始,然后将其加倍。
一旦执行上述代码片段,输出如下:
2 4 8 16 32 64 128 256 512 1024
sequenceExample()
生成了一个无限的序列,但为了保证程序可用,通过对生成无限序列的 sequenceExample()
调用 take(10)
,该序列被限制为序列中的前 10 项。
每次生成整数时,sequenceExample
下的代码块都会挂起。这就是为什么你可以在 main
中使用 forEach
打印数据,然后返回并生成一个新的数据,然后再次打印。以此类推,或者至少直到内存用完或者使用 take
到达最后一个请求的数据。