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

。_饼干妹妹 提交于 2019-12-10 19:32:39

问题


Kotlin provides take and takeWhile methods which let one to take first n items of a Sequence<T> and process them separately as another sequence, for example, drop some of them, map to other values etc.

But when I use take and takeWhile, the tail of the sequence is dropped.

Now, given a once-constrained sequence, how do I transform its arbitrary prefix to another sequence retaining the tail if it remains?

Example:

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

How do I do the same for more than one prefix?

Example:

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

Note: the question is intentionally asked and answered by the author.


回答1:


When a Sequence<T> is once-constrained, it means that it is not allowed to create more than one Iterator from it. Therefore the solution is to create one iterator and produce both the changed prefix and the remaining tail from it.

The Iterator<T>'s asSequence() method proves to be useful here as it creates a sequence backed by the iterator. What remains is just to concatenate the sequences.

Here's how you do it for one change:

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

Note that two sequences are created from the same iterator, but it's OK because

  • Sequences are evaluated lazily
  • The two sequences are used together and do not leak out
  • Evaluation of the second sequence in concatenation will start after the first one finishes

And here's how to generalize it to arbitrary number of sequence operators:

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()
}

This fold produces a chain of concatenated sequences provided by operators from the sequences backed by the iterator i, and then appends the unmodified tail to the fold result.

The limitation of this implementation is that when an operator includes takeWhille, the rejected item will be dropped and won't be emitted into the next sequence.



来源:https://stackoverflow.com/questions/35114716/how-to-modify-a-prefix-of-a-kotlin-sequence-but-retain-the-tail

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!