Skip to content

Commit

Permalink
new Helper MixIn inTwoBlocks: process a sequence/iterator in two bloc…
Browse files Browse the repository at this point in the history
…ks by a delimiter lambda, with one lambda consuming the first part of the iterable/sequence, and a second lambda that gets the result of the first lambda and the remaining iterable/sequence
  • Loading branch information
norganos committed Dec 5, 2024
1 parent 852727b commit 2660307
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package de.linkel.aoc.utils.iterables


class StoppingIterator<T>(
private val parent: Iterator<T>,
private val endPredicate: (T) -> Boolean
): Iterator<T> {
private var buffer: T? = null
private var consumed: Boolean = true

init {
if (parent.hasNext()) {
buffer = parent.next()
if (!endPredicate(buffer!!)) {
consumed = false
}
}
}

override fun hasNext(): Boolean {
return !consumed && !endPredicate(buffer!!)
}

override fun next(): T {
if (hasNext()) {
val result = buffer!!
if (parent.hasNext()) {
buffer = parent.next()
} else {
consumed = true
}
return result
} else throw NoSuchElementException()
}
}
class IteratorSequence<T>(
private val iterator: Iterator<T>
): Sequence<T> {
private var consumed = false
override fun iterator(): Iterator<T> {
if (consumed) throw NoSuchElementException()
consumed = true
return iterator
}
}

fun <A,B,T> Sequence<T>.inTwoBlocks(delimiterPredicate: (T) -> Boolean, block1: (Sequence<T>) -> A, block2: (buffer: A, Sequence<T>) -> B): B {
val iterator = this.iterator()

val first = block1(IteratorSequence(StoppingIterator(iterator, delimiterPredicate)))
return block2(first, IteratorSequence(iterator))
}
fun <A,B,T> Iterable<T>.inTwoBlocks(delimiterPredicate: (T) -> Boolean, block1: (Sequence<T>) -> A, block2: (buffer: A, Sequence<T>) -> B): B {
val iterator = this.iterator()

val first = block1(IteratorSequence(StoppingIterator(iterator, delimiterPredicate)))
return block2(first, IteratorSequence(iterator))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package de.linkel.aoc.utils.iterables

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test

class TwoBlockSequenceTest {
@Test
fun `empty sequence calls both blocks with empty sequence`() {
Assertions.assertThat(
emptySequence<String>()
.inTwoBlocks(String::isEmpty,
{ seq -> seq.count() },
{ a, seq -> a to seq.count() }
)
).isEqualTo(0 to 0)
}

@Test
fun `sequence of lines can be split on empty line`() {
Assertions.assertThat(
"""
erste zeile
zweite zeile
dritte zeile
vierte zeile
""".trimIndent()
.lineSequence()
.inTwoBlocks(String::isEmpty,
{ seq -> seq.toList() },
{ a, seq -> listOf(a, seq.toList()) }
)
).isEqualTo(listOf(
listOf("erste zeile", "zweite zeile"),
listOf("dritte zeile", "vierte zeile")
))
}

@Test
fun `empty iterable calls both blocks with empty sequence`() {
Assertions.assertThat(
emptyList<String>()
.inTwoBlocks(String::isEmpty,
{ seq -> seq.count() },
{ a, seq -> a to seq.count() }
)
).isEqualTo(0 to 0)
}

@Test
fun `iterable of lines can be split on empty line`() {
Assertions.assertThat(
"""
erste zeile
zweite zeile
dritte zeile
vierte zeile
""".trimIndent()
.lineSequence()
.toList()
.inTwoBlocks(String::isEmpty,
{ seq -> seq.toList() },
{ a, seq -> listOf(a, seq.toList()) }
)
).isEqualTo(listOf(
listOf("erste zeile", "zweite zeile"),
listOf("dritte zeile", "vierte zeile")
))
}
}

0 comments on commit 2660307

Please sign in to comment.