如何修改 Kotlin 序列的前缀但保留尾巴?

How to modify a prefix of a Kotlin sequence but retain the tail?

Kotlin 提供了 taketakeWhile 方法,可以获取 Sequence<T> 的第一个 n 项并将它们作为另一个序列分别处理,例如,drop 其中一些,map 到其他值等

但是当我使用taketakeWhile时,序列的尾部被丢弃了。

现在,给定一个 once-constrained 序列,如何将其任意前缀转换为另一个保留尾部的序列?

示例:

val seq = (1..10).asSequence().constrainOnce() 
// emits 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

val modified = seq.changePrefix { take(5).map { -1 * it } }
// emits -1, -2, -3, -4, -5, 6, 7, 8, 9, 10

如何对多个前缀执行相同的操作?

示例:

val seq = (1..10).asSequence().constrainOnce()

val modified = seq.changePrefixes(
        { take(3).map { it * -1 } },
        { drop(1).take(3).map { it * 100 } },
        { map { 0 } }
)

//emits -1, -2, -3, 500, 600, 700, 0, 0, 0

注:问题是故意问的 answered by the author.

当一个Sequence<T>被一次约束时,意味着不允许从中创建多个Iterator。因此,解决方案是创建一个迭代器并从中生成已更改的前缀和剩余的尾部。

Iterator<T>asSequence() 方法证明在这里很有用,因为它创建了一个由迭代器支持的序列。剩下的只是连接序列。

这里是一个改变的方法:

val seq = (1..10).asSequence().constrainOnce()
val modified = seq.iterator().let { iter -> 
    iter.asSequence().take(5).map { it * -1 } + iter.asSequence()
}

请注意,两个序列是从同一个迭代器创建的,但这没关系,因为

  • Sequences 被延迟评估
  • 两个序列一起使用,不泄露
  • 连接中的第二个序列的计算将在第一个序列完成后开始

以下是将其推广到任意数量的序列运算符的方法:

fun <T> Sequence<T>.changePrefixes(vararg operators: Sequence<T>.() -> Sequence<T>)
: Sequence<T> {
    val i = iterator()
    return operators.fold(emptySequence<T>()) { acc, it -> acc + i.asSequence().it() } + 
            i.asSequence()
}

fold 从迭代器 i 支持的序列中生成由 operators 提供的串联序列链,然后将未修改的尾部附加到 fold结果。

此实现的局限性在于,当运算符包含 takeWhille 时,被拒绝的项目将被丢弃,不会被发送到下一个序列中。