All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.IntervalUnion.kt Maven / Gradle / Ivy

package io.github.whathecode.kotlinx.interval


/**
 * Represents a set of all [T] values contained in a collection of disjoint, non-adjacent, [Interval]s.
 * I.e., the intervals don't overlap, and there always lie some values of [T] in between any two intervals.
 * If the collection contains no intervals, it represents an empty set.
 */
sealed interface IntervalUnion, TSize : Comparable> : Iterable>
{
    /**
     * Determines whether this is an empty set, i.e., no value of [T] is contained within.
     */
    fun isEmpty(): Boolean = none()

    /**
     * Gets the upper and lower bound of this set, and whether they are included, as a canonical interval.
     * Or, `null` if the set is empty.
     *
     * Unless this set is an [Interval], not all values lying within the upper and lower bound are part of this set.
     */
    fun getBounds(): Interval?

    /**
     * Checks whether [value] lies within this set.
     */
    operator fun contains( value: T ): Boolean

    /**
     * Return an [IntervalUnion] representing all [T] values in this set,
     * excluding all [T] values in the specified interval [toSubtract].
     */
    operator fun minus( toSubtract: Interval ): IntervalUnion

    /**
     * Return an [IntervalUnion] representing all [T] values in this set,
     * and including all [T] in the specified interval [toAdd].
     */
    operator fun plus( toAdd: Interval ): IntervalUnion

    /**
     * Determines whether [interval] has at least one value in common with this set.
     */
    fun intersects( interval: Interval ): Boolean

    /**
     * Determines whether this [IntervalUnion] represents the same set of values as the [other] union.
     */
    fun setEquals( other: IntervalUnion ): Boolean
}


/**
 * Create an [IntervalUnion] which represents a set which contains no values.
 */
@Suppress( "UNCHECKED_CAST" )
internal inline fun , TSize : Comparable> emptyIntervalUnion() =
    EmptyIntervalUnion as IntervalUnion

private data object EmptyIntervalUnion : IntervalUnion
{
    override fun getBounds(): Interval? = null

    override fun contains( value: Nothing ): Boolean = false

    override fun minus( toSubtract: Interval ): IntervalUnion = this

    override fun plus( toAdd: Interval ): IntervalUnion = toAdd

    override fun intersects( interval: Interval ): Boolean = false

    override fun setEquals( other: IntervalUnion ): Boolean = other == this

    override fun iterator(): Iterator> = emptyList>().iterator()
}


/**
 * Create an [IntervalUnion] which holds two disjoint, non-adjacent, and non-empty [IntervalUnion]'s.
 *
 * @throws IllegalArgumentException when [union1] or [union2] is empty,
 * or the two pairs are not disjoint and non-adjacent.
 */
internal fun , TSize : Comparable> intervalUnionPair(
    union1: IntervalUnion,
    union2: IntervalUnion
): IntervalUnion
{
    val compare = IntervalUnionComparison.of( union1, union2 )
    return IntervalUnionPair( compare )
}

/**
 * Combines the previously compared [IntervalUnion]'s into a single new [IntervalUnion] containing both
 * (both are returned when iterated), provided that they are disjoint and non-adjacent.
 *
 * @throws IllegalArgumentException when [IntervalUnionComparison.isSplitPair] is false.
 */
internal fun , TSize : Comparable, TUnion : IntervalUnion>
    IntervalUnionComparison.asSplitPair(): IntervalUnion = IntervalUnionPair( this )

private class IntervalUnionPair, TSize : Comparable, TUnion : IntervalUnion>(
    unionPair: IntervalUnionComparison
) : IntervalUnion
{
    init { require( unionPair.isSplitPair ) { "The pair of unions passed are not disjoint and non-adjacent." } }

    private val lower: TUnion = unionPair.lower
    private val upper: TUnion = unionPair.upper
    private val bounds: Interval = Interval(
        unionPair.lowerBounds.start, unionPair.lowerBounds.isStartIncluded,
        unionPair.upperBounds.end, unionPair.upperBounds.isEndIncluded,
        unionPair.lowerBounds.operations )

    override fun iterator(): Iterator> =
        ( lower.asSequence() + upper.asSequence() ).iterator()

    override fun getBounds(): Interval = bounds

    override fun contains( value: T ): Boolean
    {
        if ( lower is Interval<*, *> && value in lower ) return true
        if ( upper is Interval<*, *> && value in upper ) return true

        // Use bounds check to preempt unnecessary recursion for interval unions.
        if ( lower.getBounds()?.contains( value ) == true ) return value in lower
        if ( upper.getBounds()?.contains( value ) == true ) return value in upper

        return false
    }

    override fun minus( toSubtract: Interval ): IntervalUnion
    {
        // When `toSubtract` lies outside this union's bounds, no intervals are affected.
        // When it fully encompasses this union, nothing is left after subtraction.
        val pairCompare = IntervalUnionComparison.of( this, toSubtract )
        if ( pairCompare.isSplitPair ) return this
        if ( pairCompare.isFullyEncompassed && pairCompare.lower == toSubtract ) return emptyIntervalUnion()

        // Ignore `lower` or `upper if it would become empty after subtraction; recurse on the side with a remainder.
        val lowerPairCompare = IntervalUnionComparison.of( lower, toSubtract )
        if ( lowerPairCompare.isFullyEncompassed ) return upper - toSubtract
        val upperPairCompare = IntervalUnionComparison.of( upper, toSubtract )
        if ( upperPairCompare.isFullyEncompassed ) return lower - toSubtract

        // Both `lower` and `upper` have a remainder, so recursively subtract from both sides.
        return intervalUnionPair( lower - toSubtract, upper - toSubtract )
    }

    override fun plus( toAdd: Interval ): IntervalUnion
    {
        // When `toAdd` lies outside this union's bounds, prepend or append it.
        // When it fully encompasses this union, `toAdd` replaces it.
        val pairCompare = IntervalUnionComparison.of( this, toAdd )
        if ( pairCompare.isSplitPair ) return pairCompare.asSplitPair()
        if ( pairCompare.isFullyEncompassed && pairCompare.lower == toAdd ) return toAdd

        // When `toAdd` only impacts `lower` or `upper`, recursively add the new interval to the impacted union.
        // Or, if neither are impacted, the new interval must lie in between `lower` and `upper`.
        val lowerPairCompare = IntervalUnionComparison.of( lower, toAdd )
        val upperPairCompare = IntervalUnionComparison.of( upper, toAdd )
        if ( lowerPairCompare.isSplitPair )
        {
            return if ( upperPairCompare.isSplitPair ) intervalUnionPair( lowerPairCompare.asSplitPair(), upper )
            else intervalUnionPair( lower, upper + toAdd )
        }
        else if ( upperPairCompare.isSplitPair ) return intervalUnionPair( lower + toAdd, upper )

        // When both the `lower` and `upper` union are impacted, but either is fully encompassed by `toAdd`,
        // recursively add `toAdd` to the only partially impacted union.
        if ( lowerPairCompare.isFullyEncompassed ) return upper + toAdd
        if ( upperPairCompare.isFullyEncompassed ) return lower + toAdd

        // With all other options excluded, both `lower` and `upper` are partially impacted.
        // TODO: This doesn't make use of known bounds of nested unions in `upper`, only for `lower` through recursion.
        //  This can likely be optimized.
        return upper.fold( lower + toAdd ) { result, upperInterval -> result + upperInterval }
    }

    override fun intersects( interval: Interval ): Boolean
    {
        if ( lower is Interval<*, *> && lower.intersects( interval ) ) return true
        if ( upper is Interval<*, *> && upper.intersects( interval ) ) return true

        // Use bounds check to preempt unnecessary recursion for interval unions.
        if ( lower.getBounds()?.intersects( interval ) == true ) return lower.intersects( interval )
        if ( upper.getBounds()?.intersects( interval ) == true ) return upper.intersects( interval )

        return false
    }

    override fun setEquals( other: IntervalUnion ): Boolean
    {
        val iterateThis = map { it.canonicalize() }.iterator()
        val iterateOther = other.map { it.canonicalize() }.iterator()
        while ( iterateThis.hasNext() )
        {
            if ( iterateOther.hasNext() && iterateThis.next() != iterateOther.next() ) return false
        }

        return true
    }

    override fun toString(): String = joinToString( prefix = "[", postfix = "]" )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy