From 266030714228d7e3179da97bf0e92a1974019e16 Mon Sep 17 00:00:00 2001 From: Stephan Linkel Date: Thu, 5 Dec 2024 08:21:59 +0100 Subject: [PATCH] new Helper MixIn inTwoBlocks: process a sequence/iterator in two blocks 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 --- .../utils/iterables/TwoBlockSequenceMixIn.kt | 58 +++++++++++++++ .../utils/iterables/TwoBlockSequenceTest.kt | 71 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 lib/src/main/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceMixIn.kt create mode 100644 lib/src/test/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceTest.kt diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceMixIn.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceMixIn.kt new file mode 100644 index 0000000..212529d --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceMixIn.kt @@ -0,0 +1,58 @@ +package de.linkel.aoc.utils.iterables + + +class StoppingIterator( + private val parent: Iterator, + private val endPredicate: (T) -> Boolean +): Iterator { + 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( + private val iterator: Iterator +): Sequence { + private var consumed = false + override fun iterator(): Iterator { + if (consumed) throw NoSuchElementException() + consumed = true + return iterator + } +} + +fun Sequence.inTwoBlocks(delimiterPredicate: (T) -> Boolean, block1: (Sequence) -> A, block2: (buffer: A, Sequence) -> B): B { + val iterator = this.iterator() + + val first = block1(IteratorSequence(StoppingIterator(iterator, delimiterPredicate))) + return block2(first, IteratorSequence(iterator)) +} +fun Iterable.inTwoBlocks(delimiterPredicate: (T) -> Boolean, block1: (Sequence) -> A, block2: (buffer: A, Sequence) -> B): B { + val iterator = this.iterator() + + val first = block1(IteratorSequence(StoppingIterator(iterator, delimiterPredicate))) + return block2(first, IteratorSequence(iterator)) +} \ No newline at end of file diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceTest.kt new file mode 100644 index 0000000..48b2484 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/TwoBlockSequenceTest.kt @@ -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() + .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() + .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") + )) + } +}