Skip to content

Commit 43c2b3d

Browse files
authored
Merge pull request #23 from Whathecode/develop
Release 1.0.0-alpha.4
2 parents 59c50b2 + ecccdc8 commit 43c2b3d

File tree

6 files changed

+248
-3
lines changed

6 files changed

+248
-3
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ if (publishPropertiesFile.exists()) {
1616
publishProperties.load(java.io.FileInputStream(publishPropertiesFile))
1717
}
1818
group = "io.github.whathecode.kotlinx.interval"
19-
version = "1.0.0-alpha.3"
19+
version = "1.0.0-alpha.4"
2020
nexusPublishing {
2121
repositories {
2222
sonatype {

gradle.properties

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
kotlin.mpp.enableGranularSourceSetsMetadata=true
2-
kotlin.native.enableDependencyPropagation=false
31
kotlin.js.generate.executable.default=false
42

53
# Prevent out of memory errors.

kotlinx.interval.test/src/commonMain/kotlin/IntervalTest.kt

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ abstract class IntervalTest<T : Comparable<T>, TSize : Comparable<TSize>>(
2626
private val valueOperations = operations.valueOperations
2727
private val sizeOperations = operations.sizeOperations
2828

29+
/**
30+
* Value which lies as far beyond [c] as [b] lies beyond [a].
31+
*/
32+
private val d = valueOperations.unsafeAdd( c, valueOperations.unsafeSubtract( b, a ) )
33+
2934
private fun createInterval( start: T, isStartIncluded: Boolean, end: T, isEndIncluded: Boolean ) =
3035
Interval( start, isStartIncluded, end, isEndIncluded, operations )
3136
private fun createClosedInterval( start: T, end: T ): Interval<T, TSize> = createInterval( start, true, end, true )
@@ -138,4 +143,104 @@ abstract class IntervalTest<T : Comparable<T>, TSize : Comparable<TSize>>(
138143
val openIntervals = listOf( createOpenInterval( a, b ), createOpenInterval( b, a ) )
139144
openIntervals.forEach { assertTrue( a !in it && b !in it ) }
140145
}
146+
147+
@Test
148+
fun intersects_for_fully_contained_intervals()
149+
{
150+
val ad = createClosedInterval( a, d )
151+
152+
val withinIntervals = createAllInclusionTypeIntervals( b, c )
153+
val onEndPointIntervals = createAllInclusionTypeIntervals( a, b ) + createAllInclusionTypeIntervals( c, d )
154+
(withinIntervals + onEndPointIntervals).forEach { assertIntersects( ad, it, true ) }
155+
}
156+
157+
@Test
158+
fun intersects_for_partial_overlapping_interval()
159+
{
160+
val acIntervals = createAllInclusionTypeIntervals( a, c )
161+
val bdIntervals = createAllInclusionTypeIntervals( b, d )
162+
163+
for ( ac in acIntervals ) for ( bd in bdIntervals )
164+
assertIntersects( ac, bd, true )
165+
}
166+
167+
@Test
168+
fun intersects_for_nonoverlapping_intervals()
169+
{
170+
val abIntervals = createAllInclusionTypeIntervals( a, b )
171+
val cdIntervals = createAllInclusionTypeIntervals( c, d )
172+
173+
for ( ab in abIntervals ) for ( cd in cdIntervals )
174+
assertIntersects( ab, cd, false )
175+
}
176+
177+
@Test
178+
fun intersects_for_touching_endpoints()
179+
{
180+
val abWithB = createClosedInterval( a, b )
181+
val bcWithB = createClosedInterval( b, c )
182+
assertIntersects( abWithB, bcWithB, true )
183+
184+
val abWithoutB = createOpenInterval( a, b )
185+
val bcWithoutB = createOpenInterval( b, c )
186+
assertIntersects( abWithoutB, bcWithoutB, false )
187+
188+
assertIntersects( abWithB, bcWithoutB, false )
189+
assertIntersects( abWithoutB, bcWithB, false )
190+
}
191+
192+
@Test
193+
fun nonReversed_reversed_when_isReversed()
194+
{
195+
val reversed = createAllInclusionTypeIntervals( b, a )
196+
for ( original in reversed )
197+
{
198+
val normal = original.nonReversed()
199+
assertEquals( original.reverse(), normal )
200+
}
201+
}
202+
203+
@Test
204+
fun nonReversed_unchanged_when_not_isReversed()
205+
{
206+
val normal = createAllInclusionTypeIntervals( a, b )
207+
for ( original in normal )
208+
{
209+
val unchanged = original.nonReversed()
210+
assertEquals( original, unchanged )
211+
}
212+
}
213+
214+
@Test
215+
fun reverse_succeeds()
216+
{
217+
val toReverse = createAllInclusionTypeIntervals( a, b )
218+
for ( original in toReverse )
219+
{
220+
val reversed = original.reverse()
221+
assertEquals( original.start, reversed.end )
222+
assertEquals( original.isStartIncluded, reversed.isEndIncluded )
223+
assertEquals( original.end, reversed.start )
224+
assertEquals( original.isEndIncluded, reversed.isStartIncluded )
225+
}
226+
}
227+
228+
private fun assertIntersects( interval1: Interval<T, TSize>, interval2: Interval<T, TSize>, intersects: Boolean )
229+
{
230+
assertEquals( intersects, interval1.intersects( interval2 ) )
231+
assertEquals( intersects, interval2.intersects( interval1 ) )
232+
233+
// Reversing intervals should have no effect on whether they intersect or not.
234+
val interval1Reversed = interval1.reverse()
235+
val interval2Reversed = interval2.reverse()
236+
237+
assertEquals( intersects, interval1.intersects( interval2Reversed ) )
238+
assertEquals( intersects, interval2Reversed.intersects( interval1 ) )
239+
240+
assertEquals( intersects, interval1Reversed.intersects( interval2 ) )
241+
assertEquals( intersects, interval2.intersects( interval1Reversed ) )
242+
243+
assertEquals( intersects, interval1Reversed.intersects( interval2Reversed ) )
244+
assertEquals( intersects, interval2Reversed.intersects( interval1Reversed ) )
245+
}
141246
}

kotlinx.interval/src/commonMain/kotlin/Interval.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,37 @@ open class Interval<T : Comparable<T>, TSize : Comparable<TSize>>(
8686
&& ( greatestComp < 0 || (greatestComp == 0 && greatest.isIncluded) )
8787
}
8888

89+
/**
90+
* Determines whether [interval] has at least one value in common with this interval.
91+
*/
92+
fun intersects( interval: Interval<T, TSize> ): Boolean
93+
{
94+
val interval1 = nonReversed()
95+
val interval2 = interval.nonReversed()
96+
97+
val rightOfCompare: Int = interval2.start.compareTo( interval1.end )
98+
val leftOfCompare: Int = interval2.end.compareTo( interval1.start )
99+
val liesRightOf =
100+
rightOfCompare > 0 || ( rightOfCompare == 0 && !(interval2.isStartIncluded && interval1.isEndIncluded) )
101+
val liesLeftOf =
102+
leftOfCompare < 0 || ( leftOfCompare == 0 && !(interval2.isEndIncluded && interval1.isStartIncluded) )
103+
return !( liesRightOf || liesLeftOf )
104+
}
105+
106+
/**
107+
* [reverse] the interval in case [start] is greater than [end] (the interval [isReversed]),
108+
* or return the interval unchanged otherwise.
109+
* Either way, the returned interval represents the same set of all [T] values.
110+
*/
111+
fun nonReversed(): Interval<T, TSize> = if ( isReversed ) reverse() else this
112+
113+
/**
114+
* Return an interval representing the same set of all [T] values,
115+
* but swap [start] and [isStartIncluded] with [end] and [isEndIncluded].
116+
*/
117+
fun reverse(): Interval<T, TSize> =
118+
Interval( this.end, this.isEndIncluded, this.start, this.isStartIncluded, operations )
119+
89120
/**
90121
* Determines whether this interval equals [other]'s constructor parameters exactly,
91122
* i.e., not whether they represent the same set of [T] values, such as matching inverse intervals.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.whathecode.kotlinx.interval
2+
3+
4+
/**
5+
* Represents a set of all [T] values contained in a collection of non-overlapping [Interval]s,
6+
* stored in normalized form and ordered by their start values.
7+
*/
8+
sealed interface IntervalUnion<T : Comparable<T>, TSize : Comparable<TSize>> : Iterable<Interval<T, TSize>>
9+
10+
11+
/**
12+
* An [IntervalUnion] which new intervals can be added to,
13+
* as long as they are in normalized form, lie after, and don't overlap with previously added intervals.
14+
*/
15+
internal class MutableIntervalUnion<T : Comparable<T>, TSize : Comparable<TSize>> : IntervalUnion<T, TSize>
16+
{
17+
private val intervals: MutableList<Interval<T, TSize>> = mutableListOf()
18+
19+
override fun iterator(): Iterator<Interval<T, TSize>> = intervals.iterator()
20+
21+
fun add( interval: Interval<T, TSize> )
22+
{
23+
require( !interval.isReversed )
24+
{ "The interval is reversed. Normalized form is required." }
25+
require( intervals.all { it.start < interval.start } )
26+
{ "The interval lies before a previously added interval." }
27+
val last = intervals.lastOrNull()
28+
require( last == null || !interval.intersects( last ) )
29+
{ "The interval overlaps with a previously added interval." }
30+
31+
intervals.add( interval )
32+
}
33+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.github.whathecode.kotlinx.interval
2+
3+
import kotlin.test.*
4+
5+
6+
/**
7+
* Tests for [IntervalUnion].
8+
*/
9+
class MutableIntervalUnionTest
10+
{
11+
// There is no reason to believe interval unions of different types behave differently.
12+
// Testing one type should suffice.
13+
private fun createEmptyUnion() = MutableIntervalUnion<Int, UInt>()
14+
15+
@Test
16+
fun add_to_empty_succeeds()
17+
{
18+
val union = createEmptyUnion()
19+
20+
val toAdd = interval( 0, 10 )
21+
union.add( toAdd )
22+
23+
assertEquals( toAdd, union.single() )
24+
}
25+
26+
@Test
27+
fun add_to_previous_succeeds()
28+
{
29+
val union = createEmptyUnion()
30+
union.add( interval( 0, 10 ) )
31+
32+
val liesAfter = interval( 20, 30 )
33+
union.add( liesAfter )
34+
35+
assertEquals( liesAfter, union.last() )
36+
}
37+
38+
@Test
39+
fun add_non_normalized_fails()
40+
{
41+
val union = createEmptyUnion()
42+
43+
val nonNormalized = interval( 10, 0 )
44+
assertFailsWith<IllegalArgumentException> { union.add( nonNormalized ) }
45+
}
46+
47+
@Test
48+
fun add_preceding_interval_fails()
49+
{
50+
val union = createEmptyUnion()
51+
union.add( interval( 5, 10 ) )
52+
53+
val liesBefore = interval( 0, 4 )
54+
assertFailsWith<IllegalArgumentException> { union.add( liesBefore ) }
55+
}
56+
57+
@Test
58+
fun add_overlapping_interval_fails()
59+
{
60+
val union = createEmptyUnion()
61+
union.add( interval( 0, 10 ) )
62+
63+
val overlaps = interval( 5, 15 )
64+
assertFailsWith<IllegalArgumentException> { union.add( overlaps ) }
65+
val endPointOverlaps = interval( 10, 20 )
66+
assertFailsWith<IllegalArgumentException> { union.add( endPointOverlaps ) }
67+
}
68+
69+
@Test
70+
fun add_non_overlapping_endpoint_succeeds()
71+
{
72+
val union = createEmptyUnion()
73+
union.add( interval( 0, 10 ) )
74+
75+
val nonOverlappingEndPoint = interval( 10, 20, isStartIncluded = false )
76+
union.add( nonOverlappingEndPoint )
77+
}
78+
}

0 commit comments

Comments
 (0)