跳至主要內容

使用 Yiedld 和 YieldAll 生成数据

guodongAndroid大约 3 分钟

使用 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")
}

上述代码片段有几件事情:

  1. 你使用 singleValueExample() 创建了一个序列并迭代它打印每个子项。
  2. singleValueExample() 中你首先打印了一条语句,然后 yield("Apple")
  3. 加下来你同样生成了第二个数据 —— “Orange”。
  4. 最后是第三个数据 —— “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)
}

在上述代码中:

  1. 你使用 iterableExample() 创建了一个序列并迭代它打印每个子项。
  2. 你通过 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 })
}

同样的,在上述代码中:

  1. 使用 sequenceExample() 创建了一个序列,然后迭代它打印每个子项。
  2. 使用 generateSequence() 生成一个无限的整数数字并传递每次生成的整数给 yieldAll 函数。生成整数的方式是从数字 2 开始,然后将其加倍。

一旦执行上述代码片段,输出如下:

2 4 8 16 32 64 128 256 512 1024

sequenceExample() 生成了一个无限的序列,但为了保证程序可用,通过对生成无限序列的 sequenceExample() 调用 take(10),该序列被限制为序列中的前 10 项。

每次生成整数时,sequenceExample 下的代码块都会挂起。这就是为什么你可以在 main 中使用 forEach 打印数据,然后返回并生成一个新的数据,然后再次打印。以此类推,或者至少直到内存用完或者使用 take 到达最后一个请求的数据。