问题
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
Sequence
s 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