diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/CommonMath.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/CommonMath.kt new file mode 100644 index 0000000..74496dc --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/CommonMath.kt @@ -0,0 +1,100 @@ +package de.linkel.aoc.utils + +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +class CommonMath { + companion object { + fun lcm(a: Int, b: Int): Int { + if (a == 0 || b == 0) throw ArithmeticException("could not find LCM for $a and $b") + val max = max(abs(a), abs(b)) + val min = min(abs(a), abs(b)) + return generateSequence(max) { it + max } + .first { it % min == 0 } + } + + fun gcd(a: Int, b: Int): Int { + if (a == 0 || b == 0) throw ArithmeticException("could not find GCD for $a and $b") + return (min(abs(a), abs(b)) downTo 1).first { f -> + a % f == 0 && b % f == 0 + } + } + + fun lcm(a: Long, b: Long): Long { + if (a == 0L || b == 0L) throw ArithmeticException("could not find LCM for $a and $b") + val max = max(abs(a), abs(b)) + val min = min(abs(a), abs(b)) + return generateSequence(max) { it + max } + .first { it % min == 0L } + } + + fun gcd(a: Long, b: Long): Long { + if (a == 0L || b == 0L) throw ArithmeticException("could not find GCD for $a and $b") + return (min(abs(a), abs(b)) downTo 1L).first { f -> + a % f == 0L && b % f == 0L + } + } + + fun lcm(a: BigDecimal, b: BigDecimal): BigDecimal { + if (a.isZero() || b.isZero()) throw ArithmeticException("could not find LCM for $a and $b") + val max = a.abs().max(b.abs()) + val min = a.abs().min(b.abs()) + return generateSequence(max) { it + max } + .first { (it % min).isZero() } + } + + fun gcd(a: BigDecimal, b: BigDecimal): BigDecimal { + if (a.isZero() || b.isZero()) throw ArithmeticException("could not find GCD for $a and $b") + return generateSequence(a.abs().min(b.abs())) { if (it > BigDecimal.ONE) it - BigDecimal.ONE else null } + .first { f -> + (a % f).isZero() && (b % f).isZero() + } + } + + fun lcm(a: BigInteger, b: BigInteger): BigInteger { + if (a.isZero() || b.isZero()) throw ArithmeticException("could not find LCM for $a and $b") + val max = a.abs().max(b.abs()) + val min = a.abs().min(b.abs()) + return generateSequence(max) { it + max } + .first { (it % min).isZero() } + } + + fun gcd(a: BigInteger, b: BigInteger): BigInteger { + if (a.isZero() || b.isZero()) throw ArithmeticException("could not find GCD for $a and $b") + return generateSequence(a.abs().min(b.abs())) { if (it > BigInteger.ONE) it - BigInteger.ONE else null } + .first { f -> + (a % f).isZero() && (b % f).isZero() + } + } + } +} + +fun BigDecimal.isZero(): Boolean = this.compareTo(BigDecimal.ZERO) == 0 +fun BigInteger.isZero(): Boolean = this.compareTo(BigInteger.ZERO) == 0 + +fun lcm(a: Int, b: Int) = CommonMath.lcm(a, b) +fun lcm(a: Long, b: Long) = CommonMath.lcm(a, b) +fun lcm(a: BigDecimal, b: BigDecimal) = CommonMath.lcm(a, b) +fun lcm(a: BigInteger, b: BigInteger) = CommonMath.lcm(a, b) +fun gcd(a: Int, b: Int) = CommonMath.gcd(a, b) +fun gcd(a: Long, b: Long) = CommonMath.gcd(a, b) +fun gcd(a: BigDecimal, b: BigDecimal) = CommonMath.gcd(a, b) +fun gcd(a: BigInteger, b: BigInteger) = CommonMath.gcd(a, b) + +fun primes(): Sequence { + return sequence { + yield(2) + val sieve = mutableSetOf(2) + var num = 3 + while (true) { + if (sieve.none { num % it == 0 }) { + yield(num) + sieve.add(num) + } + num += 2 + } + } +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/IntRangeMixIn.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/IntRangeMixIn.kt deleted file mode 100644 index 86e71bc..0000000 --- a/lib/src/main/kotlin/de/linkel/aoc/utils/IntRangeMixIn.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.linkel.aoc.utils - -fun IntRange.intersects(other: IntRange): Boolean { - return (this.first <= other.first && this.last >= other.first) || (other.first <= this.first && other.last >= this.first) -} - -fun IntRange.extend(front: Int = 0, back: Int = 0): IntRange { - return IntRange(this.first - front, this.last + back) -} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/graph/Graph.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/graph/Graph.kt new file mode 100644 index 0000000..3640fea --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/graph/Graph.kt @@ -0,0 +1,114 @@ +package de.linkel.aoc.utils.graph + +data class Node( + val id: K, + val edges: Map +) + +class Graph( + nodes: Set> +) { + private val network = nodes.associateBy { it.id } + + fun dijkstra(start: K, isDest: (id: K) -> Boolean): List? { + val max = this.network.size + 1 + val weightMap = network.mapValues { e -> DijkstraNode(e.key, if (e.key == start) 0 else max, null) }.toMutableMap() + val points = weightMap.keys.toMutableSet() + var dest: DijkstraNode? = null + while (points.isNotEmpty()) { + val point = points.minBy { weightMap[it]!!.distance } + val pointWeightData = weightMap[point]!! + points.remove(point) + network[point]!!.edges + .filter { it.key in points } + .forEach { + weightMap[it.key] = weightMap[it.key]!!.copy(distance = pointWeightData.distance + it.value, before = point) + } + if (isDest(point)) { + dest = pointWeightData + break + } + } + return if (dest != null) { + var prev: DijkstraNode? = dest + val result = mutableListOf() + while (prev != null) { + result.add(0, prev.id) + prev = prev.before?.let { weightMap[it] } + } + result.toList() + } else { + null + } + } + + fun dfs(start: K, isDest: (id: K) -> Boolean): List? { + return dfsStep(start, isDest, emptyList()) + } + + private fun dfsStep(pos: K, isDest: (id: K) -> Boolean, path: List): List? { + return if (isDest(pos)) path + else network[pos]!!.edges.entries + .sortedBy { it.value } + .filter { it.key !in path } + .firstNotNullOfOrNull { + dfsStep(it.key, isDest, path + listOf(it.key)) + } + } + + fun bfs(start: K, isDest: (id: K) -> Boolean): List? { + if (isDest(start)) { + return listOf(start) + } + return bfsStep(start, isDest, emptyList()) + } + + private fun bfsStep(pos: K, isDest: (id: K) -> Boolean, path: List): List? { + return network[pos]!!.edges.entries + .sortedBy { it.value } + .filter { it.key !in path } + .let { possibleNext -> + possibleNext + .firstNotNullOfOrNull { + if (isDest(it.key)) path + listOf(it.key) else null + } + ?: possibleNext + .firstNotNullOfOrNull { + bfsStep(it.key, isDest, path + listOf(it.key)) + } + } + } + + data class DijkstraNode( + val id: K, + val distance: Int, + val before: K? + ) +} + +class GraphBuilder { + private val nodes = mutableMapOf>() + + fun node(id: K): GraphBuilder { + nodes[id] = nodes[id] ?: emptyMap() + return this + } + + fun edge(from: K, to: K, weight: Int = 1, bidirectional: Boolean = false): GraphBuilder { + nodes[from] = (nodes[from] ?: emptyMap()) + mapOf(to to weight) + if (bidirectional) { + nodes[to] = (nodes[to] ?: emptyMap()) + mapOf(from to weight) + } else { + nodes[to] = (nodes[to] ?: emptyMap()) + } + return this + } + + fun build(): Graph { + return Graph( + nodes.entries + .map { Node(it.key, it.value)} + .toSet() + ) + } +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/grid/Grid.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/grid/Grid.kt index a282bb1..8c7ab50 100644 --- a/lib/src/main/kotlin/de/linkel/aoc/utils/grid/Grid.kt +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/grid/Grid.kt @@ -1,7 +1,5 @@ package de.linkel.aoc.utils.grid -import java.lang.IllegalArgumentException - class Grid( origin: Point = Point(0,0), dimension: Dimension = Dimension(0,0) @@ -250,5 +248,4 @@ class Grid( val distance: Int, val before: Point? ) - } diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/SequenceMixIn.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/ConcatSequence.kt similarity index 98% rename from lib/src/main/kotlin/de/linkel/aoc/utils/SequenceMixIn.kt rename to lib/src/main/kotlin/de/linkel/aoc/utils/mixins/ConcatSequence.kt index 571a315..3be4aa0 100644 --- a/lib/src/main/kotlin/de/linkel/aoc/utils/SequenceMixIn.kt +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/ConcatSequence.kt @@ -1,4 +1,4 @@ -package de.linkel.aoc.utils +package de.linkel.aoc.utils.mixins fun Sequence.prepend(element: T): Sequence { return if (this is ConcatSequence) { diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/IntRangeMixIn.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/IntRangeMixIn.kt new file mode 100644 index 0000000..c8e70ea --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/IntRangeMixIn.kt @@ -0,0 +1,18 @@ +package de.linkel.aoc.utils.mixins + +fun IntRange.intersects(other: IntRange): Boolean { + return (this.first <= other.first && this.last >= other.first) || (other.first <= this.first && other.last >= this.first) +} + +fun IntRange.intersect(other: IntRange): IntRange { + return if (this.intersects(other)) IntRange(kotlin.math.max(this.first, other.first), kotlin.math.min(this.last, other.last)) + else IntRange.EMPTY +} + +fun IntRange.extend(front: Int = 0, back: Int = 0): IntRange { + return IntRange(this.first - front, this.last + back) +} + +fun IntRange.move(offset: Int): IntRange { + return IntRange(this.first + offset, this.last + offset) +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/LongRangeMixIn.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/LongRangeMixIn.kt new file mode 100644 index 0000000..6625c0e --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/LongRangeMixIn.kt @@ -0,0 +1,17 @@ +package de.linkel.aoc.utils.mixins + +fun LongRange.intersects(other: LongRange): Boolean { + return (this.first <= other.first && this.last >= other.first) || (other.first <= this.first && other.last >= this.first) +} + +fun LongRange.intersect(other: LongRange): LongRange { + return LongRange(kotlin.math.max(this.first, other.first), kotlin.math.min(this.last, other.last)) +} + +fun LongRange.extend(front: Long = 0, back: Long = 0): LongRange { + return LongRange(this.first - front, this.last + back) +} + +fun LongRange.move(offset: Long): LongRange { + return LongRange(this.first + offset, this.last + offset) +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/NumberIterables.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/NumberIterables.kt new file mode 100644 index 0000000..20c80e4 --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/mixins/NumberIterables.kt @@ -0,0 +1,21 @@ +package de.linkel.aoc.utils.mixins + +import de.linkel.aoc.utils.CommonMath +import java.math.BigDecimal + +fun Iterable.sum() = this.reduce(BigDecimal::plus) +fun Sequence.sum(): BigDecimal = this.reduce(BigDecimal::times) + +fun Iterable.product() = this.reduce(BigDecimal::times) +fun Sequence.product() = this.reduce(BigDecimal::times) +fun Iterable.product() = this.reduce(Int::times) +fun Sequence.product() = this.reduce(Int::times) +fun Iterable.product() = this.reduce(Long::times) +fun Sequence.product() = this.reduce(Long::times) + +fun Iterable.lcm() = this.reduce(CommonMath::lcm) +fun Sequence.lcm() = this.reduce(CommonMath::lcm) +fun Iterable.lcm() = this.reduce(CommonMath::lcm) +fun Sequence.lcm() = this.reduce(CommonMath::lcm) +fun Iterable.lcm() = this.reduce(CommonMath::lcm) +fun Sequence.lcm() = this.reduce(CommonMath::lcm) diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/AbstractNumberRangeSet.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/AbstractNumberRangeSet.kt new file mode 100644 index 0000000..0f09fe0 --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/AbstractNumberRangeSet.kt @@ -0,0 +1,123 @@ +package de.linkel.aoc.utils.rangeset + +import de.linkel.aoc.utils.mixins.ConcatIterator + +abstract class AbstractNumberRangeSet( + ranges: Iterable, + private val factory: RangeFactory +): Iterable where O : AbstractNumberRangeSet, + T : Comparable, + R : ClosedRange, + R : Iterable, + C: Number { + val segments: List + val first: T + val last: T + val size: C + + init { + val cache = mutableListOf() + ranges.filter { !it.isEmpty() }.sortedBy { it.start }.forEach { range -> + if (cache.isNotEmpty() && (cache.last().endInclusive >= range.start || factory.incr(cache.last().endInclusive) == range.start)) { + val last = cache.last() + cache[cache.size - 1] = factory.build(last.start, if (last.endInclusive > range.endInclusive) last.endInclusive else range.endInclusive) + } else { + cache.add(range) + } + } + println("$ranges -> $cache") + segments = cache.toList() + first = segments.firstOrNull()?.start ?: factory.maxValue + last = segments.lastOrNull()?.endInclusive ?: factory.minValue + size = factory.sumCounts(segments.map { factory.count(it) }) + } + + fun min(): T = if (isNotEmpty()) first else throw NoSuchElementException() + fun max(): T = if (isNotEmpty()) last else throw NoSuchElementException() + + fun isEmpty() = segments.isEmpty() + fun isNotEmpty() = segments.isNotEmpty() + fun contains(value: T) = segments.any { it.contains(value) } + fun contains(value: R) = segments.any { it.contains(value.start) && it.contains(value.endInclusive) } + fun intersects(value: R) = segments.any { factory.intersects(it, value) } + + override fun iterator(): Iterator { + return ConcatIterator(segments.map { it.iterator() }) + } + + protected abstract fun copy(ranges: Iterable): O + + operator fun minus(range: R): O { + return copy( + segments + .flatMap { factory.without(it, range) } + ) + } + + operator fun minus(other: O): O { + return copy( + other.segments.fold(segments) { ranges, sub -> + ranges.flatMap { + factory.without(it, sub) + } + } + ) + } + + operator fun plus(range: R): O { + return copy( + segments + listOf(range) + ) + } + + operator fun plus(other: O): O { + return copy( + segments + other.segments + ) + } + + fun intersect(range: R): O { + return copy( + segments + .map { factory.intersect(it, range) } + ) + } + + fun intersect(other: O): O { + return copy( + segments + .flatMap { range -> + other.segments + .map { factory.intersect(range, it) } + } + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AbstractNumberRangeSet<*, *, *, *> + + if (factory != other.factory) return false + if (segments != other.segments) return false + if (first != other.first) return false + if (last != other.last) return false + if (size != other.size) return false + + return true + } + + override fun hashCode(): Int { + var result = factory.hashCode() + result = 31 * result + segments.hashCode() + result = 31 * result + first.hashCode() + result = 31 * result + last.hashCode() + result = 31 * result + size.hashCode() + return result + } + + override fun toString(): String { + return segments.joinToString(",").ifEmpty { "empty" } + } +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSet.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSet.kt new file mode 100644 index 0000000..8ac7aad --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSet.kt @@ -0,0 +1,54 @@ +package de.linkel.aoc.utils.rangeset + +import de.linkel.aoc.utils.mixins.intersect +import de.linkel.aoc.utils.mixins.intersects + +class IntRangeSetFactory: RangeFactory { + companion object { + val INSTANCE = IntRangeSetFactory() + } + + override fun build(start: Int, endInclusive: Int) = IntRange(start, endInclusive) + + override fun incr(a: Int) = a +1 + + override fun add(a: Int, b: Int) = a + b + + override fun subtract(a: Int, b: Int) = a - b + + override fun count(range: IntRange) = range.last - range.first + 1 + + override fun sumCounts(counts: Iterable) = counts.sum() + + override fun without(a: IntRange, b: IntRange): Collection { + return if (a.first >= b.first && a.last <= b.last) emptyList() // a completely inside b -> nothing left + else if (a.first < b.first && a.last > b.last) listOf(IntRange(a.first, b.first - 1), IntRange(b.last + 1, a.last)) // b completely inside a -> b cuts a in 2 parts + else if (a.first < b.first && a.last > b.first) listOf(IntRange(a.first, b.first - 1)) // b cuts a upper end + else if (a.last > b.last && a.first < b.last) listOf(IntRange(b.last + 1, a.last)) // b cuts a lower end + else listOf(a) // no intersection -> a stays the same + } + + override fun intersects(a: IntRange, b: IntRange): Boolean { + return a.intersects(b) + } + + override fun intersect(a: IntRange, b: IntRange): IntRange { + return a.intersect(b) + } + + override val maxValue = Int.MAX_VALUE + override val minValue = Int.MIN_VALUE +} + +class IntRangeSet( + ranges: Iterable +): AbstractNumberRangeSet( + ranges, + IntRangeSetFactory.INSTANCE +) { + override fun copy(ranges: Iterable) = IntRangeSet(ranges) +} + +fun IntRange.toRangeSet(): IntRangeSet { + return IntRangeSet(listOf(this)) +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSet.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSet.kt new file mode 100644 index 0000000..ff1990a --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSet.kt @@ -0,0 +1,54 @@ +package de.linkel.aoc.utils.rangeset + +import de.linkel.aoc.utils.mixins.intersect +import de.linkel.aoc.utils.mixins.intersects + +class LongRangeSetFactory: RangeFactory { + companion object { + val INSTANCE = LongRangeSetFactory() + } + + override fun build(start: Long, endInclusive: Long) = LongRange(start, endInclusive) + + override fun incr(a: Long) = a +1 + + override fun add(a: Long, b: Long) = a + b + + override fun subtract(a: Long, b: Long) = a - b + + override fun count(range: LongRange) = range.last - range.first + 1 + + override fun sumCounts(counts: Iterable) = counts.sum() + + override fun without(a: LongRange, b: LongRange): Collection { + return if (a.first >= b.first && a.last <= b.last) emptyList() // a completely inside b -> nothing left + else if (a.first < b.first && a.last > b.last) listOf(LongRange(a.first, b.first - 1), LongRange(b.last + 1, a.last)) // b completely inside a -> b cuts a in 2 parts + else if (a.first < b.first && a.last > b.first) listOf(LongRange(a.first, b.first - 1)) // b cuts a upper end + else if (a.last > b.last && a.first < b.last) listOf(LongRange(b.last + 1, a.last)) // b cuts a lower end + else listOf(a) // no intersection -> a stays the same + } + + override fun intersects(a: LongRange, b: LongRange): Boolean { + return a.intersects(b) + } + + override fun intersect(a: LongRange, b: LongRange): LongRange { + return a.intersect(b) + } + + override val maxValue = Long.MAX_VALUE + override val minValue = Long.MIN_VALUE +} + +class LongRangeSet( + ranges: Iterable +): AbstractNumberRangeSet( + ranges, + LongRangeSetFactory.INSTANCE +) { + override fun copy(ranges: Iterable) = LongRangeSet(ranges) +} + +fun LongRange.toRangeSet(): LongRangeSet { + return LongRangeSet(listOf(this)) +} diff --git a/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/RangeSet.kt b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/RangeSet.kt new file mode 100644 index 0000000..db955c8 --- /dev/null +++ b/lib/src/main/kotlin/de/linkel/aoc/utils/rangeset/RangeSet.kt @@ -0,0 +1,19 @@ +package de.linkel.aoc.utils.rangeset + + +interface RangeFactory where T : Comparable, + R : ClosedRange, + R : Iterable, + C: Number { + fun build(start: T, endInclusive: T): R + fun incr(a: T): T + fun add(a: T, b: T): T + fun subtract(a: T, b: T): T + fun count(range: R): C + fun sumCounts(counts: Iterable): C + fun without(a: R, b: R): Collection + fun intersects(a: R, b: R): Boolean + fun intersect(a: R, b: R): R + val maxValue: T + val minValue: T +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/MathTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/MathTest.kt new file mode 100644 index 0000000..077fd91 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/MathTest.kt @@ -0,0 +1,77 @@ +package de.linkel.aoc.utils + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import java.math.BigDecimal +import java.math.BigInteger + +class MathTest { + @Test + fun `lcm 1 and 3 is 3`() { + Assertions.assertThat(lcm(1, 3)).isEqualTo(3) + Assertions.assertThat(lcm(1L, 3L)).isEqualTo(3L) + Assertions.assertThat(lcm(BigDecimal.ONE, BigDecimal("3"))).isEqualByComparingTo(BigDecimal("3")) + Assertions.assertThat(lcm(BigInteger.ONE, BigInteger("3"))).isEqualByComparingTo(BigInteger("3")) + } + + @Test + fun `lcm 21 and 77 is 231`() { + Assertions.assertThat(lcm(21, 77)).isEqualTo(231) + Assertions.assertThat(lcm(21L, 77L)).isEqualTo(231L) + Assertions.assertThat(lcm(BigDecimal("21"), BigDecimal("77"))).isEqualByComparingTo(BigDecimal("231")) + Assertions.assertThat(lcm(BigInteger("21"), BigInteger("77"))).isEqualByComparingTo(BigInteger("231")) + } + + @Test + fun `lcm -21 and 77 is 231`() { + Assertions.assertThat(lcm(-21, 77)).isEqualTo(231) + Assertions.assertThat(lcm(-21L, 77L)).isEqualTo(231L) + Assertions.assertThat(lcm(BigDecimal("-21"), BigDecimal("77"))).isEqualByComparingTo(BigDecimal("231")) + Assertions.assertThat(lcm(BigInteger("-21"), BigInteger("77"))).isEqualByComparingTo(BigInteger("231")) + } + + @Test + fun `lcm 2 and 0 throws Exception`() { + Assertions.assertThatThrownBy { lcm(2, 0) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { lcm(2L, 0L) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { lcm(BigDecimal("2"), BigDecimal.ZERO) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { lcm(BigInteger("2"), BigInteger.ZERO) }.isInstanceOf(ArithmeticException::class.java) + } + + @Test + fun `gcd 1 and 3 is 1`() { + Assertions.assertThat(gcd(1, 3)).isEqualTo(1) + Assertions.assertThat(gcd(1L, 3L)).isEqualTo(1L) + Assertions.assertThat(gcd(BigDecimal.ONE, BigDecimal("3"))).isEqualByComparingTo(BigDecimal.ONE) + Assertions.assertThat(gcd(BigInteger.ONE, BigInteger("3"))).isEqualByComparingTo(BigInteger.ONE) + } + + @Test + fun `gcd 2 and 0 throws Exception`() { + Assertions.assertThatThrownBy { gcd(2, 0) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { gcd(2L, 0L) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { gcd(BigDecimal("2"), BigDecimal.ZERO) }.isInstanceOf(ArithmeticException::class.java) + Assertions.assertThatThrownBy { gcd(BigInteger("2"), BigInteger.ZERO) }.isInstanceOf(ArithmeticException::class.java) + } + + @Test + fun `gcd 48 and 332 is 4`() { + Assertions.assertThat(gcd(48, 332)).isEqualTo(4) + Assertions.assertThat(gcd(48L, 332)).isEqualTo(4L) + Assertions.assertThat(gcd(BigDecimal("48"), BigDecimal("332"))).isEqualByComparingTo(BigDecimal("4")) + Assertions.assertThat(gcd(BigInteger("48"), BigInteger("332"))).isEqualByComparingTo(BigInteger("4")) + } + + @Test + fun `gcd 48 and -332 is 4`() { + Assertions.assertThat(gcd(48, -332)).isEqualTo(4) + Assertions.assertThat(gcd(48L, -332L)).isEqualTo(4L) + Assertions.assertThat(gcd(BigDecimal("48"), BigDecimal("-332"))).isEqualByComparingTo(BigDecimal("4")) + Assertions.assertThat(gcd(BigInteger("48"), BigInteger("-332"))).isEqualByComparingTo(BigInteger("4")) + } + + @Test + fun `primes under 100`() { + Assertions.assertThat(primes().take(50).filter { it < 100}.toList()).isEqualTo(listOf(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97)) + } +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeExtendTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeExtendTest.kt new file mode 100644 index 0000000..47cd2d3 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeExtendTest.kt @@ -0,0 +1,64 @@ +package de.linkel.aoc.utils.iterables + +import de.linkel.aoc.utils.mixins.extend +import de.linkel.aoc.utils.mixins.move +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class IntRangeExtendTest { + @Test + fun `2 to 4 extended by 1 at front is 1 to 4`() { + Assertions.assertThat( + (2..4).extend(front = 1) + ).isEqualTo(1..4) + } + + @Test + fun `2 to 4 extended by 2 at front is 0 to 4`() { + Assertions.assertThat( + (2..4).extend(front = 2) + ).isEqualTo(0..4) + } + + @Test + fun `2 to 4 extended by 3 at front is -1 to 4`() { + Assertions.assertThat( + (2..4).extend(front = 3) + ).isEqualTo(-1..4) + } + + @Test + fun `2 to 4 extended by 1 at back is 2 to 5`() { + Assertions.assertThat( + (2..4).extend(back = 1) + ).isEqualTo(2..5) + } + + @Test + fun `2 to 4 extended by 2 at back is 2 to 6`() { + Assertions.assertThat( + (2..4).extend(back = 2) + ).isEqualTo(2..6) + } + + @Test + fun `2 to 4 extended by 2 at front and back is 0 to 6`() { + Assertions.assertThat( + (2..4).extend(front = 2, back = 2) + ).isEqualTo(0..6) + } + + @Test + fun `2 to 4 moved by 2 is 4 to 6`() { + Assertions.assertThat( + (2..4).move(2) + ).isEqualTo(4..6) + } + + @Test + fun `2 to 4 moved by -1 is 1 to 3`() { + Assertions.assertThat( + (2..4).move(-1) + ).isEqualTo(1..3) + } +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/IntRangeMixInTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeIntersectTest.kt similarity index 64% rename from lib/src/test/kotlin/de/linkel/aoc/utils/IntRangeMixInTest.kt rename to lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeIntersectTest.kt index 56f54b6..fc0a2a9 100644 --- a/lib/src/test/kotlin/de/linkel/aoc/utils/IntRangeMixInTest.kt +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/IntRangeIntersectTest.kt @@ -1,9 +1,11 @@ -package de.linkel.aoc.utils +package de.linkel.aoc.utils.iterables +import de.linkel.aoc.utils.mixins.intersect +import de.linkel.aoc.utils.mixins.intersects import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test -class IntRangeMixInTest { +class IntRangeIntersectTest { @Test fun `1 to 3 and 4 to 6 do not intersect`() { Assertions.assertThat( @@ -11,6 +13,13 @@ class IntRangeMixInTest { ).isFalse() } + @Test + fun `intersection of 1 to 3 and 4 to 6 is empty`() { + Assertions.assertThat( + (1..3).intersect(4..6) + ).isEmpty() + } + @Test fun `10 to 30 and 4 to 6 do not intersect`() { Assertions.assertThat( @@ -25,6 +34,16 @@ class IntRangeMixInTest { ).isTrue() } + @Test + fun `intersection of 1 to 3 and 2 to 4 is 2,3`() { + Assertions.assertThat( + (1..3).intersect(2..4) + ).isNotEmpty() + Assertions.assertThat( + (1..3).intersect(2..4) + ).isEqualTo(2..3) + } + @Test fun `1 to 3 and 3 to 4 intersect`() { Assertions.assertThat( @@ -73,46 +92,4 @@ class IntRangeMixInTest { (1..5).intersects(5..5) ).isTrue() } - - @Test - fun `2 to 4 extended by 1 at front is 1 to 4`() { - Assertions.assertThat( - (2..4).extend(front = 1) - ).isEqualTo(1..4) - } - - @Test - fun `2 to 4 extended by 2 at front is 0 to 4`() { - Assertions.assertThat( - (2..4).extend(front = 2) - ).isEqualTo(0..4) - } - - @Test - fun `2 to 4 extended by 3 at front is -1 to 4`() { - Assertions.assertThat( - (2..4).extend(front = 3) - ).isEqualTo(-1..4) - } - - @Test - fun `2 to 4 extended by 1 at back is 2 to 5`() { - Assertions.assertThat( - (2..4).extend(back = 1) - ).isEqualTo(2..5) - } - - @Test - fun `2 to 4 extended by 2 at back is 2 to 6`() { - Assertions.assertThat( - (2..4).extend(back = 2) - ).isEqualTo(2..6) - } - - @Test - fun `2 to 4 extended by 2 at front and back is 0 to 6`() { - Assertions.assertThat( - (2..4).extend(front = 2, back = 2) - ).isEqualTo(0..6) - } } diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeExtendTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeExtendTest.kt new file mode 100644 index 0000000..0c5d731 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeExtendTest.kt @@ -0,0 +1,64 @@ +package de.linkel.aoc.utils.iterables + +import de.linkel.aoc.utils.mixins.extend +import de.linkel.aoc.utils.mixins.move +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class LongRangeExtendTest { + @Test + fun `2 to 4 extended by 1 at front is 1 to 4`() { + Assertions.assertThat( + (2L..4L).extend(front = 1L) + ).isEqualTo(1L..4L) + } + + @Test + fun `2 to 4 extended by 2 at front is 0 to 4`() { + Assertions.assertThat( + (2L..4L).extend(front = 2L) + ).isEqualTo(0L..4L) + } + + @Test + fun `2 to 4 extended by 3 at front is -1 to 4`() { + Assertions.assertThat( + (2L..4L).extend(front = 3L) + ).isEqualTo(-1L..4L) + } + + @Test + fun `2 to 4 extended by 1 at back is 2 to 5`() { + Assertions.assertThat( + (2L..4L).extend(back = 1L) + ).isEqualTo(2L..5L) + } + + @Test + fun `2 to 4 extended by 2 at back is 2 to 6`() { + Assertions.assertThat( + (2L..4L).extend(back = 2L) + ).isEqualTo(2L..6L) + } + + @Test + fun `2 to 4 extended by 2 at front and back is 0 to 6`() { + Assertions.assertThat( + (2L..4L).extend(front = 2L, back = 2L) + ).isEqualTo(0L..6L) + } + + @Test + fun `2 to 4 moved by 2 is 4 to 6`() { + Assertions.assertThat( + (2L..4L).move(2L) + ).isEqualTo(4L..6L) + } + + @Test + fun `2 to 4 moved by -1 is 1 to 3`() { + Assertions.assertThat( + (2L..4L).move(-1L) + ).isEqualTo(1L..3L) + } +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeIntersectTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeIntersectTest.kt new file mode 100644 index 0000000..bb4975b --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/LongRangeIntersectTest.kt @@ -0,0 +1,95 @@ +package de.linkel.aoc.utils.iterables + +import de.linkel.aoc.utils.mixins.intersect +import de.linkel.aoc.utils.mixins.intersects +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class LongRangeIntersectTest { + @Test + fun `1 to 3 and 4 to 6 do not intersect`() { + Assertions.assertThat( + (1L..3L).intersects(4L..6L) + ).isFalse() + } + + @Test + fun `intersection of 1 to 3 and 4 to 6 is empty`() { + Assertions.assertThat( + (1L..3L).intersect(4L..6L) + ).isEmpty() + } + + @Test + fun `10 to 30 and 4 to 6 do not intersect`() { + Assertions.assertThat( + (10L..30L).intersects(4L..6L) + ).isFalse() + } + + @Test + fun `1 to 3 and 2 to 4 intersect`() { + Assertions.assertThat( + (1L..3L).intersects(2L..4L) + ).isTrue() + } + + @Test + fun `intersection of 1 to 3 and 2 to 4 is 2,3`() { + Assertions.assertThat( + (1L..3L).intersect(2L..4L) + ).isNotEmpty() + Assertions.assertThat( + (1L..3L).intersect(2L..4L) + ).isEqualTo(2L..3L) + } + + @Test + fun `1 to 3 and 3 to 4 intersect`() { + Assertions.assertThat( + (1L..3L).intersects(3L..4L) + ).isTrue() + } + + @Test + fun `-1 to 1 and 0 to 4 intersect`() { + Assertions.assertThat( + (-1L..1L).intersects(0L..4L) + ).isTrue() + } + + @Test + fun `-3 to -1 and -2 to 4 intersect`() { + Assertions.assertThat( + (-3L..-1L).intersects(-2L..4L) + ).isTrue() + } + + @Test + fun `1 to 5 and 5 to 10 intersect`() { + Assertions.assertThat( + (1L..5L).intersects(5L..10L) + ).isTrue() + } + + @Test + fun `1 to 5 and 4 to 5 intersect`() { + Assertions.assertThat( + (1L..5L).intersects(4L..5L) + ).isTrue() + } + + @Test + fun `1 to 5 and 3 to 4 intersect`() { + Assertions.assertThat( + (1..5).intersects(3..4) + ).isTrue() + } + + @Test + fun `1 to 5 and 5 to 5 intersect`() { + Assertions.assertThat( + (1..5).intersects(5..5) + ).isTrue() + } +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/SequenceConcatTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/SequenceConcatTest.kt similarity index 92% rename from lib/src/test/kotlin/de/linkel/aoc/utils/SequenceConcatTest.kt rename to lib/src/test/kotlin/de/linkel/aoc/utils/iterables/SequenceConcatTest.kt index 851d7f7..12c6f78 100644 --- a/lib/src/test/kotlin/de/linkel/aoc/utils/SequenceConcatTest.kt +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/iterables/SequenceConcatTest.kt @@ -1,5 +1,8 @@ -package de.linkel.aoc.utils +package de.linkel.aoc.utils.iterables +import de.linkel.aoc.utils.mixins.append +import de.linkel.aoc.utils.mixins.plus +import de.linkel.aoc.utils.mixins.prepend import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSetTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSetTest.kt new file mode 100644 index 0000000..fcff083 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/IntRangeSetTest.kt @@ -0,0 +1,117 @@ +package de.linkel.aoc.utils.rangeset + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class IntRangeSetTest { + @Test + fun `2-10 without 0-5 is 6-10`() { + val result = (2..10).toRangeSet() - (0..5) + Assertions.assertThat(result.toList()).isEqualTo(listOf(6, 7, 8, 9, 10)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(5) + Assertions.assertThat(result.first).isEqualTo(6) + Assertions.assertThat(result.last).isEqualTo(10) + } + + @Test + fun `2-10 without 5-6 is 2-4+7-10`() { + val result = (2..10).toRangeSet() - (5..6) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 3, 4, 7, 8, 9, 10)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(7) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(10) + } + + @Test + fun `2-5 without 3-3 is 2,4,5`() { + val result = (2..5).toRangeSet() - (3..3) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 4, 5)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(3) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(5) + } + + @Test + fun `2-5 without 2-5 is empty`() { + val result = (2..5).toRangeSet() - (2..5) + Assertions.assertThat(result.toList()).isEqualTo(emptyList()) + Assertions.assertThat(result).hasSize(0) + Assertions.assertThat(result.isEmpty()).isTrue() + Assertions.assertThat(result.isNotEmpty()).isFalse() + } + + @Test + fun `2-5 without 0-10 is empty`() { + val result = (2..5).toRangeSet() - (0..10) + Assertions.assertThat(result.toList()).isEqualTo(emptyList()) + Assertions.assertThat(result).hasSize(0) + Assertions.assertThat(result.isEmpty()).isTrue() + Assertions.assertThat(result.isNotEmpty()).isFalse() + } + + @Test + fun `2-5 without 3-2 is 2,3,4,5`() { + val result = (2..5).toRangeSet() - IntRange.EMPTY + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 3, 4, 5)) + Assertions.assertThat(result).hasSize(4) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(5) + } + + @Test + fun `4-10 without 5-6 plus 2-3 is 2-4+7-10`() { + val result = (4..10).toRangeSet() - (5..6) + (2..3) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 3, 4, 7, 8, 9, 10)) + Assertions.assertThat(result).hasSize(7) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(10) + } + + @Test + fun `4-10 without 5-6 plus 2-6 is 2-10`() { + val result = (4..10).toRangeSet() - (5..6) + (2..6) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 3, 4, 5, 6, 7, 8, 9, 10)) + Assertions.assertThat(result).hasSize(9) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(10) + } + + @Test + fun `4-10 without 5-6 plus 2-6 is 2-10 in rangeSets`() { + val result = (4..10).toRangeSet() - (5..6).toRangeSet() + (2..6).toRangeSet() + Assertions.assertThat(result.toList()).isEqualTo(listOf(2, 3, 4, 5, 6, 7, 8, 9, 10)) + Assertions.assertThat(result).hasSize(9) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(10) + } + + @Test + fun `3-4 plus 4-5 equals 3-5`() { + Assertions.assertThat((3..4).toRangeSet() + (4..5)).isEqualTo((3..5).toRangeSet()) + } + + @Test + fun `equality works`() { + Assertions.assertThat((3..4).toRangeSet()).isEqualTo((3..4).toRangeSet()) + Assertions.assertThat((3..4).toRangeSet().hashCode()).isEqualTo((3..4).toRangeSet().hashCode()) + } + + @Test + fun `3-4 intersect 4-5 equals 4-4`() { + Assertions.assertThat((3..4).toRangeSet().intersect(4..5)).isEqualTo((4..4).toRangeSet()) + Assertions.assertThat((3..4).toRangeSet().intersect((4..5).toRangeSet())).isEqualTo((4..4).toRangeSet()) + } + + @Test + fun `3-4 plus 5-6 is merged into 1 sequence`() { + Assertions.assertThat( + ((3..4).toRangeSet() + (5..6)).segments + ).hasSize(1) + } +} diff --git a/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSetTest.kt b/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSetTest.kt new file mode 100644 index 0000000..d683540 --- /dev/null +++ b/lib/src/test/kotlin/de/linkel/aoc/utils/rangeset/LongRangeSetTest.kt @@ -0,0 +1,117 @@ +package de.linkel.aoc.utils.rangeset + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test + +class LongRangeSetTest { + @Test + fun `2-10 without 0-5 is 6-10`() { + val result = (2L..10L).toRangeSet() - (0L..5L) + Assertions.assertThat(result.toList()).isEqualTo(listOf(6L, 7L, 8L, 9L, 10L)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(5) + Assertions.assertThat(result.first).isEqualTo(6L) + Assertions.assertThat(result.last).isEqualTo(10L) + } + + @Test + fun `2-10 without 5-6 is 2-4+7-10`() { + val result = (2L..10L).toRangeSet() - (5L..6L) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 3L, 4L, 7L, 8L, 9L, 10L)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(7) + Assertions.assertThat(result.first).isEqualTo(2L) + Assertions.assertThat(result.last).isEqualTo(10L) + } + + @Test + fun `2-5 without 3-3 is 2,4,5`() { + val result = (2L..5L).toRangeSet() - (3L..3L) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 4L, 5L)) + Assertions.assertThat(result.isEmpty()).isFalse() + Assertions.assertThat(result.isNotEmpty()).isTrue() + Assertions.assertThat(result).hasSize(3) + Assertions.assertThat(result.first).isEqualTo(2L) + Assertions.assertThat(result.last).isEqualTo(5L) + } + + @Test + fun `2-5 without 2-5 is empty`() { + val result = (2L..5L).toRangeSet() - (2L..5L) + Assertions.assertThat(result.toList()).isEqualTo(emptyList()) + Assertions.assertThat(result).hasSize(0) + Assertions.assertThat(result.isEmpty()).isTrue() + Assertions.assertThat(result.isNotEmpty()).isFalse() + } + + @Test + fun `2-5 without 0-10 is empty`() { + val result = (2L..5L).toRangeSet() - (0L..10L) + Assertions.assertThat(result.toList()).isEqualTo(emptyList()) + Assertions.assertThat(result).hasSize(0) + Assertions.assertThat(result.isEmpty()).isTrue() + Assertions.assertThat(result.isNotEmpty()).isFalse() + } + + @Test + fun `2-5 without 3-2 is 2,3,4,5`() { + val result = (2L..5L).toRangeSet() - LongRange.EMPTY + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 3L, 4L, 5L)) + Assertions.assertThat(result).hasSize(4) + Assertions.assertThat(result.first).isEqualTo(2) + Assertions.assertThat(result.last).isEqualTo(5) + } + + @Test + fun `4-10 without 5-6 plus 2-3 is 2-4+7-10`() { + val result = (4L..10L).toRangeSet() - (5L..6L) + (2L..3L) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 3L, 4L, 7L, 8L, 9L, 10L)) + Assertions.assertThat(result).hasSize(7) + Assertions.assertThat(result.first).isEqualTo(2L) + Assertions.assertThat(result.last).isEqualTo(10L) + } + + @Test + fun `4-10 without 5-6 plus 2-6 is 2-10`() { + val result = (4L..10L).toRangeSet() - (5L..6L) + (2L..6L) + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)) + Assertions.assertThat(result).hasSize(9) + Assertions.assertThat(result.first).isEqualTo(2L) + Assertions.assertThat(result.last).isEqualTo(10L) + } + + @Test + fun `4-10 without 5-6 plus 2-6 is 2-10 in rangeSets`() { + val result = (4L..10L).toRangeSet() - (5L..6L).toRangeSet() + (2L..6L).toRangeSet() + Assertions.assertThat(result.toList()).isEqualTo(listOf(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)) + Assertions.assertThat(result).hasSize(9) + Assertions.assertThat(result.first).isEqualTo(2L) + Assertions.assertThat(result.last).isEqualTo(10L) + } + + @Test + fun `3-4 plus 4-5 equals 3-5`() { + Assertions.assertThat((3L..4L).toRangeSet() + (4L..5L)).isEqualTo((3L..5L).toRangeSet()) + } + + @Test + fun `equality works`() { + Assertions.assertThat((3L..4L).toRangeSet()).isEqualTo((3L..4L).toRangeSet()) + Assertions.assertThat((3L..4L).toRangeSet().hashCode()).isEqualTo((3L..4L).toRangeSet().hashCode()) + } + + @Test + fun `3-4 intersect 4-5 equals 4-4`() { + Assertions.assertThat((3L..4L).toRangeSet().intersect(4L..5L)).isEqualTo((4L..4L).toRangeSet()) + Assertions.assertThat((3L..4L).toRangeSet().intersect((4L..5L).toRangeSet())).isEqualTo((4L..4L).toRangeSet()) + } + + @Test + fun `3-4 plus 5-6 is merged into 1 sequence`() { + Assertions.assertThat( + ((3L..4L).toRangeSet() + (5L..6L)).segments + ).hasSize(1) + } +}