Added ranges from Kotlin 1.1

This commit is contained in:
Igor Chevdar
2017-03-22 17:45:53 +03:00
parent 8ec4cb48f9
commit fc9638dbd4
2 changed files with 422 additions and 0 deletions
@@ -477,3 +477,121 @@ public infix fun Byte.downTo(to: Short): IntProgression =
*/
public infix fun Short.downTo(to: Short): IntProgression =
IntProgression.fromClosedRange(this.toInt(), to.toInt(), -1)
/**
* Represents a range of floating point numbers.
* Extends [ClosedRange] interface providing custom operation [lessThanOrEquals] for comparing values of range domain type.
*
* This interface is implemented by floating point ranges returned by [Float.rangeTo] and [Double.rangeTo] operators to
* achieve IEEE-754 comparison order instead of total order of floating point numbers.
*/
public interface ClosedFloatingPointRange<T: Comparable<T>> : ClosedRange<T> {
override fun contains(value: T): Boolean = lessThanOrEquals(start, value) && lessThanOrEquals(value, endInclusive)
override fun isEmpty(): Boolean = !lessThanOrEquals(start, endInclusive)
/**
* Compares two values of range domain type and returns true if first is less than or equal to second.
*/
fun lessThanOrEquals(a: T, b: T): Boolean
}
/**
* Represents a range of [Comparable] values.
*/
private open class ComparableRange<T: Comparable<T>> (
override val start: T,
override val endInclusive: T
): ClosedRange<T> {
override fun equals(other: Any?): Boolean {
return other is ComparableRange<*> && (isEmpty() && other.isEmpty() ||
start == other.start && endInclusive == other.endInclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * start.hashCode() + endInclusive.hashCode()
}
override fun toString(): String = "$start..$endInclusive"
}
/**
* A closed range of values of type `Double`.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
private class ClosedDoubleRange (
start: Double,
endInclusive: Double
) : ClosedFloatingPointRange<Double> {
private val _start = start
private val _endInclusive = endInclusive
override val start: Double get() = _start
override val endInclusive: Double get() = _endInclusive
override fun lessThanOrEquals(a: Double, b: Double): Boolean = a <= b
override fun contains(value: Double): Boolean = value >= _start && value <= _endInclusive
override fun isEmpty(): Boolean = !(_start <= _endInclusive)
override fun equals(other: Any?): Boolean {
return other is ClosedDoubleRange && (isEmpty() && other.isEmpty() ||
_start == other._start && _endInclusive == other._endInclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * _start.hashCode() + _endInclusive.hashCode()
}
override fun toString(): String = "$_start..$_endInclusive"
}
/**
* A closed range of values of type `Float`.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
private class ClosedFloatRange (
start: Float,
endInclusive: Float
): ClosedFloatingPointRange<Float> {
private val _start = start
private val _endInclusive = endInclusive
override val start: Float get() = _start
override val endInclusive: Float get() = _endInclusive
override fun lessThanOrEquals(a: Float, b: Float): Boolean = a <= b
override fun contains(value: Float): Boolean = value >= _start && value <= _endInclusive
override fun isEmpty(): Boolean = !(_start <= _endInclusive)
override fun equals(other: Any?): Boolean {
return other is ClosedFloatRange && (isEmpty() && other.isEmpty() ||
_start == other._start && _endInclusive == other._endInclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * _start.hashCode() + _endInclusive.hashCode()
}
override fun toString(): String = "$_start..$_endInclusive"
}
/**
* Creates a range from this [Comparable] value to the specified [that] value.
*
* This value needs to be smaller than [that] value, otherwise the returned range will be empty.
*/
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
/**
* Creates a range from this [Double] value to the specified [other] value.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
public operator fun Double.rangeTo(that: Double): ClosedFloatingPointRange<Double> = ClosedDoubleRange(this, that)
/**
* Creates a range from this [Float] value to the specified [other] value.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
public operator fun Float.rangeTo(that: Float): ClosedFloatingPointRange<Float> = ClosedFloatRange(this, that)
@@ -143,3 +143,307 @@ public infix fun Short.until(to: Short): IntRange {
return this.toInt() .. (to.toInt() - 1).toInt()
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Int>.contains(value: Byte): Boolean {
return contains(value.toInt())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Long>.contains(value: Byte): Boolean {
return contains(value.toLong())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Short>.contains(value: Byte): Boolean {
return contains(value.toShort())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Double>.contains(value: Byte): Boolean {
return contains(value.toDouble())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Float>.contains(value: Byte): Boolean {
return contains(value.toFloat())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Int>.contains(value: Double): Boolean {
return value.toIntExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Long>.contains(value: Double): Boolean {
return value.toLongExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Byte>.contains(value: Double): Boolean {
return value.toByteExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Short>.contains(value: Double): Boolean {
return value.toShortExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Float>.contains(value: Double): Boolean {
return contains(value.toFloat())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Int>.contains(value: Float): Boolean {
return value.toIntExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Long>.contains(value: Float): Boolean {
return value.toLongExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Byte>.contains(value: Float): Boolean {
return value.toByteExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Short>.contains(value: Float): Boolean {
return value.toShortExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Double>.contains(value: Float): Boolean {
return contains(value.toDouble())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Long>.contains(value: Int): Boolean {
return contains(value.toLong())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Byte>.contains(value: Int): Boolean {
return value.toByteExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Short>.contains(value: Int): Boolean {
return value.toShortExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Double>.contains(value: Int): Boolean {
return contains(value.toDouble())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Float>.contains(value: Int): Boolean {
return contains(value.toFloat())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Int>.contains(value: Long): Boolean {
return value.toIntExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Byte>.contains(value: Long): Boolean {
return value.toByteExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Short>.contains(value: Long): Boolean {
return value.toShortExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Double>.contains(value: Long): Boolean {
return contains(value.toDouble())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Float>.contains(value: Long): Boolean {
return contains(value.toFloat())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Int>.contains(value: Short): Boolean {
return contains(value.toInt())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Long>.contains(value: Short): Boolean {
return contains(value.toLong())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Byte>.contains(value: Short): Boolean {
return value.toByteExactOrNull().let { if (it != null) contains(it) else false }
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Double>.contains(value: Short): Boolean {
return contains(value.toDouble())
}
/**
* Checks if the specified [value] belongs to this range.
*/
public operator fun ClosedRange<Float>.contains(value: Short): Boolean {
return contains(value.toFloat())
}
/**
* Returns a progression from this value down to the specified [to] value with the step -1.
*
* The [to] value has to be less than this value.
*/
public infix fun Short.downTo(to: Long): LongProgression {
return LongProgression.fromClosedRange(this.toLong(), to, -1L)
}
/**
* Returns a progression that goes over the same range in the opposite direction with the same step.
*/
public fun LongProgression.reversed(): LongProgression {
return LongProgression.fromClosedRange(last, first, -step)
}
/**
* Returns a progression that goes over the same range in the opposite direction with the same step.
*/
public fun CharProgression.reversed(): CharProgression {
return CharProgression.fromClosedRange(last, first, -step)
}
internal fun Int.toByteExactOrNull(): Byte? {
return if (this in Byte.MIN_VALUE.toInt()..Byte.MAX_VALUE.toInt()) this.toByte() else null
}
internal fun Long.toByteExactOrNull(): Byte? {
return if (this in Byte.MIN_VALUE.toLong()..Byte.MAX_VALUE.toLong()) this.toByte() else null
}
internal fun Short.toByteExactOrNull(): Byte? {
return if (this in Byte.MIN_VALUE.toShort()..Byte.MAX_VALUE.toShort()) this.toByte() else null
}
internal fun Double.toByteExactOrNull(): Byte? {
return if (this in Byte.MIN_VALUE.toDouble()..Byte.MAX_VALUE.toDouble()) this.toByte() else null
}
internal fun Float.toByteExactOrNull(): Byte? {
return if (this in Byte.MIN_VALUE.toFloat()..Byte.MAX_VALUE.toFloat()) this.toByte() else null
}
internal fun Long.toIntExactOrNull(): Int? {
return if (this in Int.MIN_VALUE.toLong()..Int.MAX_VALUE.toLong()) this.toInt() else null
}
internal fun Double.toIntExactOrNull(): Int? {
return if (this in Int.MIN_VALUE.toDouble()..Int.MAX_VALUE.toDouble()) this.toInt() else null
}
internal fun Float.toIntExactOrNull(): Int? {
return if (this in Int.MIN_VALUE.toFloat()..Int.MAX_VALUE.toFloat()) this.toInt() else null
}
internal fun Double.toLongExactOrNull(): Long? {
return if (this in Long.MIN_VALUE.toDouble()..Long.MAX_VALUE.toDouble()) this.toLong() else null
}
internal fun Float.toLongExactOrNull(): Long? {
return if (this in Long.MIN_VALUE.toFloat()..Long.MAX_VALUE.toFloat()) this.toLong() else null
}
internal fun Int.toShortExactOrNull(): Short? {
return if (this in Short.MIN_VALUE.toInt()..Short.MAX_VALUE.toInt()) this.toShort() else null
}
internal fun Long.toShortExactOrNull(): Short? {
return if (this in Short.MIN_VALUE.toLong()..Short.MAX_VALUE.toLong()) this.toShort() else null
}
internal fun Double.toShortExactOrNull(): Short? {
return if (this in Short.MIN_VALUE.toDouble()..Short.MAX_VALUE.toDouble()) this.toShort() else null
}
internal fun Float.toShortExactOrNull(): Short? {
return if (this in Short.MIN_VALUE.toFloat()..Short.MAX_VALUE.toFloat()) this.toShort() else null
}
/**
* Ensures that this value lies in the specified [range].
*
* @return this value if it's in the [range], or `range.start` if this value is less than `range.start`, or `range.endInclusive` if this value is greater than `range.endInclusive`.
*/
public fun <T: Comparable<T>> T.coerceIn(range: ClosedFloatingPointRange<T>): T {
if (range.isEmpty()) throw IllegalArgumentException("Cannot coerce value to an empty range: $range.")
return when {
// this < start equiv to this <= start && !(this >= start)
range.lessThanOrEquals(this, range.start) && !range.lessThanOrEquals(range.start, this) -> range.start
// this > end equiv to this >= end && !(this <= end)
range.lessThanOrEquals(range.endInclusive, this) && !range.lessThanOrEquals(this, range.endInclusive) -> range.endInclusive
else -> this
}
}