Add Sequence.coalesce()

This commit is contained in:
kageru 2019-10-07 23:19:48 +02:00
parent 51d14216e9
commit a8137c6ee4
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
3 changed files with 97 additions and 0 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
gradle*
.gradle/
build/
out/

View File

@ -0,0 +1,47 @@
package moe.kageru.sekwences
/**
* Returns a sequences that optionally merges adjacent elements.
* Heavily inspired by Itertools.coalesce() from the Rust crate.
*
* https://docs.rs/itertools/0.8.0/itertools/trait.Itertools.html#method.coalesce
*/
fun <T> Sequence<T>.coalesce(merger: (T, T) -> T?): Sequence<T> {
return if (this.iterator().hasNext()) {
CoalescingSequence(this, merger)
} else {
emptySequence()
}
}
class CoalescingSequence<T>(private val source: Sequence<T>, private inline val merger: (T, T) -> T?) : Sequence<T> {
override fun iterator(): Iterator<T> {
return CoalescingIterator(source.iterator(), merger)
}
}
class CoalescingIterator<T>(private val source: Iterator<T>, private inline val merger: (T, T) -> T?) : Iterator<T> {
private var previous: T = if (source.hasNext()) source.next() else error("Please don’t pass empty iterators in here")
private var hasNext = true
override fun hasNext() = hasNext
override tailrec fun next(): T {
if (!source.hasNext()) {
hasNext = false
return previous
}
val current = source.next()
val merged = merger(previous, current)
// If the elements can’t be merged, return the first, then save the second for next time.
return if (merged == null) {
val ret = previous
previous = current
ret
} else {
previous = merged
next()
}
}
}

View File

@ -0,0 +1,48 @@
package moe.kageru.sekwences
import io.kotlintest.shouldBe
import io.kotlintest.specs.ShouldSpec
class SekwencesTest : ShouldSpec({
"should merge numbers" {
val input = sequenceOf(1, 2, 3, 4, 5, 6, 4)
val output = input.coalesce { first, second ->
val sum = first + second
if (sum <= 10) sum else null
}.toList()
output shouldBe listOf(10, 5, 10)
}
"should merge collections" {
val input = sequenceOf("apple", "apricot", "banana", "peach", "pear", "plum")
val output = input.map { listOf(it) }.coalesce { first, second ->
if (first.first()[0] == second.first()[0]) {
first + second
} else {
null
}
}.toList()
output shouldBe listOf(listOf("apple", "apricot"), listOf("banana"), listOf("peach", "pear", "plum"))
}
"should handle empty sequences" {
val input = emptySequence<Int>()
val output = input.coalesce { _, _ -> 0 }
output shouldBe emptySequence()
}
"should handle single elements" {
val input = sequenceOf(1)
val output = input.coalesce { a, b -> if (a > 5) a else b }.toList()
output shouldBe listOf(1)
}
"should not modify sequences that can’t be merged" {
val input = listOf(6, 7, 34, 8)
val output = input.asSequence().coalesce { first, second ->
val sum = first + second
if (sum <= 10) sum else null
}.toList()
output shouldBe input
}
})