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

it.unibo.alchemist.util.Ranges.kt Maven / Gradle / Ivy

Go to download

Abstract, incarnation independent implementations of the Alchemist's interfaces. Provides support for those who want to write incarnations.

There is a newer version: 35.0.1
Show newest version
/*
 * Copyright (C) 2010-2023, Danilo Pianini and contributors
 * listed, for each module, in the respective subproject's build.gradle.kts file.
 *
 * This file is part of Alchemist, and is distributed under the terms of the
 * GNU General Public License, with a linking exception,
 * as described in the file LICENSE in the Alchemist distribution's top directory.
 */

package it.unibo.alchemist.util

import it.unibo.alchemist.model.geometry.Vector2D
import it.unibo.alchemist.util.Ranges.minus
import org.apache.commons.lang3.ObjectUtils

/**
 * A collection of extension functions that enrich [ClosedRange].
 */
object Ranges {
    /**
     * Creates a [ClosedRange] from a couple of unordered values.
     */
    fun > rangeFromUnordered(bound1: T, bound2: T): ClosedRange =
        ObjectUtils.min(bound1, bound2)..ObjectUtils.max(bound1, bound2)

    /**
     * Checks whether two ranges coincide. It's a different way of checking if they're equals,
     * which doesn't depend on their actual implementation. Note that the way you obtain a range
     * may influence the actual class used, and you can have two coincident ranges which are not
     * equal because of different classes.
     */
    fun > ClosedRange.coincidesWith(other: ClosedRange): Boolean =
        start == other.start && endInclusive == other.endInclusive

    /**
     * Checks whether the range contains the [other] one.
     * This method is faster than checking if the range contains both of the endpoints of [other].
     */
    fun > ClosedRange.contains(other: ClosedRange): Boolean =
        start <= other.start && endInclusive >= other.endInclusive

    /**
     * Checks whether the range intersects the [other] one.
     */
    fun > ClosedRange.intersects(other: ClosedRange): Boolean =
        start <= other.endInclusive && endInclusive >= other.start

    /*
     * Finds the intersection between two ranges which are guaranteed to intersect.
     */
    private fun > ClosedRange.unsafeIntersect(other: ClosedRange): ClosedRange =
        ObjectUtils.max(start, other.start)..ObjectUtils.min(endInclusive, other.endInclusive)

    /**
     * Finds the intersection between two ranges, the resulting range may feature a single value
     * (if the ranges only share an endpoint) or can be null, if they don't intersect at all.
     */
    fun > ClosedRange.intersect(other: ClosedRange): ClosedRange? = when {
        intersects(other) -> unsafeIntersect(other)
        else -> null
    }

    /**
     * Checks whether two ranges intersect, excluding their bounds (i.e., excluding both
     * [ClosedRange.start] and [ClosedRange.endInclusive]). This means false is returned in
     * case the ranges share a single endpoint.
     */
    fun > ClosedRange.intersectsBoundsExcluded(other: ClosedRange): Boolean =
        start < other.endInclusive && endInclusive > other.start

    /**
     * Performs a subtraction between ranges. The operation can produce an empty list (e.g. if
     * the current range is contained in the [other] one), a list featuring a single element,
     * or a list featuring two elements (e.g. if the current range contains the [other] one).
     */
    infix operator fun > ClosedRange.minus(other: ClosedRange): List> = when {
        other.contains(this) -> emptyList()
        !intersects(other) -> listOf(this)
        start >= other.start -> listOf(rangeFromUnordered(endInclusive, other.endInclusive))
        endInclusive <= other.endInclusive -> listOf(rangeFromUnordered(start, other.start))
        else -> listOf(rangeFromUnordered(start, other.start), rangeFromUnordered(endInclusive, other.endInclusive))
    }

    /**
     * Subtracts all the given ranges from the current one. See [ClosedRange.minus].
     */
    fun > ClosedRange.subtractAll(others: List>): List> = when {
        others.isEmpty() -> listOf(this)
        else -> (this - others.first()).flatMap { it.subtractAll(others.drop(1)) }
    }

    /**
     * Given a non-empty list of points represented as vectors, this method finds the extreme
     * coordinates (i.e. min and max coordinates) either on the X-axis or the Y-axis. [getXCoords]
     * indicates which coordinates to extract, these are used to create the returned [ClosedRange].
     */
    private fun > List.findExtremeCoords(getXCoords: Boolean): ClosedRange {
        val selector = { v: V -> v.x.takeIf { getXCoords } ?: v.y }
        val min = minByOrNull(selector)?.run(selector)
        val max = maxByOrNull(selector)?.run(selector)
        require(min != null && max != null) { "no point could be found" }
        return min..max
    }

    /**
     * Given a non-empty list of points represented as vectors, this method finds the extreme coordinates
     * (i.e. min and max coordinates) on the X-axis, these are used to create the returned [ClosedRange].
     */
    fun > List.findExtremeCoordsOnX(): ClosedRange = findExtremeCoords(true)

    /**
     * Given a non-empty list of points represented as vectors, this method finds the extreme coordinates
     * (i.e. min and max coordinates) on the Y-axis, these are used to create the returned [ClosedRange].
     */
    fun > List.findExtremeCoordsOnY(): ClosedRange = findExtremeCoords(false)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy