Why Kotlin doesn't have a Decimal Progression? - kotlin

Recently I faced with a problem iterating through decimal numbers with a decimal step and I was wondered, why Kotlin has Progressions only for Int, Long and Char.
I understand, that there could be some caveats with decimal numbers. But still. We just want to have a start BigDecimal number, end BigDecimal number and then iterate through them with BigDecimal step.
Q: So, why there is no any progressions for not integer numbers? Thank you.
P.S.: Here is a sample code of possible implementation (I took sources for Int and adapted to BigDecimal):
/**
* Returns a progression that goes over the same range with the given step.
*/
public infix fun BigDecimalProgression.step(step: BigDecimal): BigDecimalProgression {
if (step <= java.math.BigDecimal.ZERO) throw IllegalArgumentException("Step must be positive, was: $step.")
return BigDecimalProgression.fromClosedRange(first, last, if (this.step > java.math.BigDecimal.ZERO) step else -step)
}
/**
* A progression of values of type `BigDecimal`.
*/
public open class BigDecimalProgression
internal constructor
(
start: BigDecimal,
endInclusive: BigDecimal,
step: BigDecimal
) : Iterable<BigDecimal> {
init {
if (step == BigDecimal.ZERO) throw kotlin.IllegalArgumentException("Step must be non-zero")
}
/**
* The first element in the progression.
*/
public val first: BigDecimal = start
/**
* The last element in the progression.
*/
public val last: BigDecimal = getProgressionLastElement(start, endInclusive, step)
/**
* The step of the progression.
*/
public val step: BigDecimal = step
override fun iterator(): BigDecimalIterator = BigDecimalProgressionIterator(first, last, step)
/** Checks if the progression is empty. */
public open fun isEmpty(): Boolean = if (step > BigDecimal.ZERO) first > last else first < last
override fun equals(other: Any?): Boolean =
other is BigDecimalProgression && (isEmpty() && other.isEmpty() ||
first == other.first && last == other.last && step == other.step)
override fun hashCode(): Int =
if (isEmpty()) -1 else (31 * (31 * first.hashCode() + last.hashCode()) + step.hashCode())
override fun toString(): String = if (step > BigDecimal.ZERO) "$first..$last step $step" else "$first downTo $last step ${-step}"
companion object {
/**
* Creates BigDecimalProgression within the specified bounds of a closed range.
* The progression starts with the [rangeStart] value and goes toward the [rangeEnd] value not excluding it, with the specified [step].
* In order to go backwards the [step] must be negative.
*/
public fun fromClosedRange(rangeStart: BigDecimal, rangeEnd: BigDecimal, step: BigDecimal): BigDecimalProgression = BigDecimalProgression(rangeStart, rangeEnd, step)
}
}
fun getProgressionLastElement(start: BigDecimal, end: BigDecimal, step: BigDecimal): BigDecimal {
if (step > BigDecimal.ZERO) {
return start + BigDecimal(((end - start) / step).toInt()) * step
} else if (step < BigDecimal.ZERO) {
return start - BigDecimal(((start - end) / -step).toInt()) * -step
} else {
throw kotlin.IllegalArgumentException("Step is zero.")
}
}
/** An iterator over a sequence of values of type `BigDecimal`. */
public abstract class BigDecimalIterator : Iterator<BigDecimal> {
override final fun next() = nextBigDecimal()
/** Returns the next value in the sequence without boxing. */
public abstract fun nextBigDecimal(): BigDecimal
}
/**
* An iterator over a progression of values of type `BigDecimal`.
* #property step the number by which the value is incremented on each step.
*/
internal class BigDecimalProgressionIterator(first: BigDecimal, last: BigDecimal, val step: BigDecimal) : BigDecimalIterator() {
private val finalElement = last
private var hasNext: Boolean = if (step > BigDecimal.ZERO) first <= last else first >= last
private var next = if (hasNext) first else finalElement
override fun hasNext(): Boolean = hasNext
override fun nextBigDecimal(): BigDecimal {
val value = next
if (value >= finalElement) {
if (!hasNext) throw kotlin.NoSuchElementException()
hasNext = false
}
else {
next += step
}
return value
}
}

As it said in the documentation for ranges:
Floating point numbers (Double, Float) do not define their rangeTo
operator, and the one provided by the standard library for generic
Comparable types is used instead:
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
The range returned by this function cannot be used for iteration. You
will have to use some other kind of loop since you can't use ranges.
They simply do not define.

Related

Getting list of enums from bitmask in Kotlin

I have an enum class SettingsVisibility inside an database entity, which I am converting to a bitmask to store in the database. I am trying to reverse the conversion to bitmask, and get a list of enums as a return value. So if I have an enum with values ONE(1), TWO(2), FOUR(4), then it'll store as Enum(7). I want to take 7 and convert it to {ONE, TWO, FOUR}.
My code is below. I have the SettingsVisibility enum with integer values which are stored in the DB. When I try to retrieve from the database, Objectbox will use the given PropertyConvertor to marshall/unmarshall the data. When I want to convertToEntityProperty, it should return a list of just the saved enums, but at the moment it returns a list of all the enums. I can pass a databaseValue of 12 and it will return all enums instead of just 2 (LOCATION AND PAYMENTS).
I think the issue is the usage of enumClass.enumConstants because it gets all the values, but then the filter doesn't work on this, so I am stuck.
#Entity
data class Settings(
#Id override var id: Long = 0,
#Convert(converter = DocumentVisibilityConverter::class, dbType = Int::class)
val showItems: List<SettingsVisibility>
) : Identifiable<Long> {
lateinit var organisation: ToOne<Organisation>
constructor() : this(
showItems = emptyList(),
)
enum class SettingsVisibility(override val bit: Int) : Flags {
USERS(1),
FINANCE(2),
LOCATION(4),
PAYMENTS(8),
MESSAGES(16),
ERRORS(32),
CANCELLATIONS(64)
}
internal class DocumentVisibilityConverter
: BoxConverters.EnumFlagConverter<SettingsVisibility>(SettingsVisibility::class.java)
}
So for example, if I store the first 3, the database value will be 7 (1+2+4).
The database is ObjectBox and here are the property converters:
abstract class EnumFlagConverter<E>(private val enumClass: Class<E>) : PropertyConverter<List<E>, Int> where E : Enum<E>, E : Flags {
override fun convertToDatabaseValue(entityProperty: List<E>?): Int? {
return entityProperty?.toBitMask()?.value
}
override fun convertToEntityProperty(databaseValue: Int?): List<E>? {
return databaseValue?.let(::BitMask)?.enabledValues(enumClass)
}
}
class BitMask(val value: Int)
interface Flags {
val bit: Int
fun toBitMask() = BitMask(bit)
fun <T> BitMask.enabledValues(enumClass: Class<T>): List<T>? where T : Enum<T>, T : Flags? {
return enumClass.enumConstants?.filter(::hasFlag)
}
infix fun <T : Flags?> BitMask.hasFlag(flag: T): Boolean {
if (value == 0 || (value > 0 && flag?.bit == 0)) {
return false
}
return true
}
Maybe the logic in hasFlag is wrong, because I think that just gets every enum if it isn't 0.
Answer was to replace return true, with:
if (flag?.bit?.toByte() == null) {
return false
}
return (this.value.toByte().and(flag.bit.toByte()) == flag.bit.toByte())
This is basically: bit & mask == bit

Kotlin - What happens when a range is created using .. or how it performs comparations?

I recently noticed a code refactor from:
if (date < minDate || date > maxDate)
to
if (date !in minDate..maxDate)
to which my main worry was that using range would create an "array" or some list of all the milliseconds between minDate and maxDate
I tried researching some of the kotlin internals but couldn't get a final answer on what would happen on that case.
Supposedly:
In Kotlin in checks are translated to the corresponding contains
calls
No, it does not create an array of every possible value (because an array would be inefficient for that, even if we did need to store every value, which we don't).
This is the source for the ClosedRange interface, which the .. range operator translates into (comments removed):
public interface ClosedRange<T: Comparable<T>> {
public val start: T
public val endInclusive: T
public fun isEmpty(): Boolean = start > endInclusive
public operator fun contains(value: T): Boolean =
value >= start && value <= endInclusive
}
As you can see the type it ranges over (T) must implement Comparable<T>. This allows the implementation to do a straight comparison of the value and the start and endInclusive of the range. You can see this in the implementation of contains(value: T).
According to Range class implementation in kotlin, it won't create a list and comparison will be done as follows (contains is called when comparing ranges), based on LongProgression class (created on Long ranges).
/**
* A range of values of type `Long`.
*/
public class LongRange(start: Long, endInclusive: Long) : LongProgression(start, endInclusive, 1), ClosedRange<Long> {
override val start: Long get() = first
override val endInclusive: Long get() = last
override fun contains(value: Long): Boolean = first <= value && value <= last
override fun isEmpty(): Boolean = first > last
override fun equals(other: Any?): Boolean =
other is LongRange && (isEmpty() && other.isEmpty() ||
first == other.first && last == other.last)
override fun hashCode(): Int =
if (isEmpty()) -1 else (31 * (first xor (first ushr 32)) + (last xor (last ushr 32))).toInt()
override fun toString(): String = "$first..$last"
companion object {
/** An empty range of values of type Long. */
public val EMPTY: LongRange = LongRange(1, 0)
}
}

Convert collection of numbers to range formatted string with Kotlin

I have a very large list of numbers. I need to pass this list of numbers as a URL query parameter. Since these lists can get so large, it could potentially cause the request URL to exceed the allowed length of a URL; also, it's a bit difficult to debug a string of sequential numbers (E.G. 1,2,3,..,500,782). To remedy these issues, I would like to convert the sequential number list string to one that is formatted using a range notation (E.G. -5..-3,1..500,782). How do I create this range notation string using Kotlin and how do I parse the string back to a collection of numbers also using Kotlin?
This will convert a Collection<Int> to a string that uses the "range notation" specified:
fun Collection<Int>.toRangesString(): String {
if (this.isEmpty()) {
return ""
}
if (this.size <= 2) {
return this.toSortedSet().joinToString(",")
}
val rangeStrings = mutableListOf<String>()
var start: Int? = null
var prev: Int? = null
for (num in this.toSortedSet()) {
if (prev == null) {
start = num
prev = num
continue
}
if (num != (prev + 1)) {
_addRangeString(rangeStrings, start!!, prev)
start = num
prev = num
continue
}
prev = num
}
if (start != null) {
_addRangeString(rangeStrings, start, prev!!)
}
return rangeStrings.joinToString(",")
}
private fun _addRangeString(rangeStrings: MutableList<String>, start: Int, prev: Int) {
rangeStrings.add(
when {
(start == prev) -> start.toString()
((start + 1) == prev) -> "${start},${prev}"
else -> "${start}..${prev}"
}
)
}
...and this will parse those range notated strings into a Set<Int>:
fun parseRangesString(str: String): Set<Int> {
if (str.isBlank()) {
return setOf()
}
val ranges = str.trim().split(",")
val numbers = mutableListOf<Int>()
for (range in ranges) {
if (range.contains("..")) {
val (start, end) = range.split("..")
numbers.addAll(start.toInt()..end.toInt())
continue
}
numbers.add(range.toInt())
}
return numbers.toSet()
}
...and, finally, even better than using a huge collection of numbers, you can use Kotlin's IntRange (or LongRange) class:
fun toIntRanges(str: String): Collection<IntRange> = _toRanges(str, ::_createIntRange)
fun toLongRanges(str: String): Collection<LongRange> = _toRanges(str, ::_createLongRange)
private fun <T : ClosedRange<*>> _toRanges(str: String, createRange: (start: String, end: String) -> T): Collection<T> {
if (str.isBlank()) {
return listOf()
}
val rangeStrs = str.trim().split(",")
val ranges = mutableListOf<T>()
for (rangeStr in rangeStrs) {
if (rangeStr.contains("..")) {
val (start, end) = rangeStr.split("..")
ranges.add(createRange(start, end))
continue
}
ranges.add(createRange(rangeStr, rangeStr))
}
return ranges.toList()
}
private fun _createIntRange(start: String, end: String) = IntRange(start.toInt(), end.toInt())
private fun _createLongRange(start: String, end: String) = LongRange(start.toLong(), end.toLong())

Operator overloading Kotlin

I'm new to kotlin and I'm working on operators overloading for a custom class I defined. The class is called "Rational" and represents a rational number, like for example 117/1098. Class is defined as below and I have overloaded a bunch of operators, like plus, minus, times and so on. However I'm uncertain about what I have to do to overload "in" operator.
Here is my class:
data class Rational(val rational: String) {
private val numerator: BigInteger
private val denominator: BigInteger
init {
val splitted = rational.split("/")
numerator = splitted[0].toBigInteger()
denominator = when (splitted[1]) {
"0" -> throw Exception("not allowed")
else -> splitted[1].toBigInteger()
}
}
operator fun plus(number: Rational): Rational {
val gcm = denominator * number.denominator
val numerator = (gcm / denominator) * numerator + (gcm / number.denominator) * number.numerator
return Rational("$numerator/$gcm")
}
operator fun minus(number: Rational): Rational {
val gcm = denominator * number.denominator
val numerator = (gcm / denominator) * numerator - (gcm / number.denominator) * number.numerator
return Rational("$numerator/$gcm")
}
operator fun times(number: Rational): Rational {
val numerator = numerator * number.numerator
val denominator = denominator * number.denominator
return Rational("$numerator/$denominator")
}
operator fun div(number: Rational): Rational {
val numerator = numerator * number.denominator
val denominator = denominator * number.numerator
return Rational("$numerator/$denominator")
}
operator fun compareTo(number: Rational): Int {
val ratio = this.numerator.toFloat() / this.denominator.toFloat()
val numberRatio = number.numerator.toFloat() / number.denominator.toFloat()
if (ratio > numberRatio) {
return 1
} else if (ratio == numberRatio) {
return 0
}
return -1
}
operator fun unaryMinus(): Rational {
val inverseNumerator = -numerator
return Rational("$inverseNumerator/$denominator")
}
operator fun unaryPlus(): Rational {
return Rational("$numerator/$denominator")
}
operator fun rangeTo(end: Rational): Any {
var range: MutableList<Rational> = arrayListOf()
val startNumerator = this.numerator.toInt()
val endNumerator = end.numerator.toInt()
var index = 0
if (this.denominator == end.denominator) {
for (i in startNumerator..endNumerator) {
range.add(index, Rational("$i/$denominator"))
}
}
return range
}
operator fun contains(number: Rational): Boolean {
if (this.denominator % number.denominator == 0.toBigInteger()
&& this.numerator <= number.numerator) {
return true
}
return false
}
override fun toString(): String {
val gcd = numerator.gcd(denominator)
return if (gcd != null) {
val newNumerator = numerator / gcd
val newDenominator = denominator / gcd
"$newNumerator/$newDenominator"
} else {
"$numerator/$denominator"
}
}
}
infix fun Int.divBy(denominator: Int): Rational {
if (denominator == 0) {
throw Exception("denominator 0 not allowed")
}
return Rational("$this/$denominator")
}
infix fun Long.divBy(denominator: Long): Rational {
if (denominator == 0L) {
throw Exception("denominator 0 not allowed")
}
return Rational("$this/$denominator")
}
infix fun BigInteger.divBy(denominator: BigInteger): Rational {
if (denominator == 0.toBigInteger()) {
throw Exception("denominator 0 not allowed")
}
return Rational("$this/$denominator")
}
fun String.toRational(): Rational {
return Rational(this)
}
And here is my main body that obviously still doesn't compile:
fun main() {
val half = 1 divBy 2
val third = 1 divBy 3
val twoThirds = 2 divBy 3
println(half in third..twoThirds) // this line does not compile beacause in operator is not defined for the class
}
I guess I have to override "rangeTo" operator but I'm uncertain about the operator prototype. I there somebody that can please help me to get to the right track?
The way to make in work is for the third..twoThirds call to return something that has a contains(Rational) method, which is what the in call translates to.
One way to do this is to return a ClosedRange<Rational> here, like so:
operator fun rangeTo(end: Rational): ClosedRange<Rational> {
return object : ClosedRange<Rational> {
override val endInclusive: Rational = end
override val start: Rational = this#Rational
}
}
This puts a type constraint on Rational, as a ClosedRange needs a Comparable implementation to be able to determine whether a value belongs in it. You can do this by implementing the Comparable interface, and then adding operator to your existing compareTo operator (plus it's a good practice to rename the parameter to match the interface):
data class Rational(val rational: String) : Comparable<Rational> {
...
override operator fun compareTo(other: Rational): Int {
val ratio = this.numerator.toFloat() / this.denominator.toFloat()
val numberRatio = other.numerator.toFloat() / other.denominator.toFloat()
if (ratio > numberRatio) {
return 1
} else if (ratio == numberRatio) {
return 0
}
return -1
}
}
You could also avoid the conversion to floats entirely by using this implementation instead, as suggested in the comment below by #gidds:
override operator fun compareTo(other: Rational): Int {
return (numerator * other.denominator - denominator * other.numerator).signum()
}
Also, your current contains implementation could probably be discarded, as you no longer need it, and it functions rather oddly.
To add something other than the direct answer here: as #Eugene Petrenko suggested in their answer, it would be practical to add a couple constructors other than the one that uses a String, for example one that takes two Ints, and one that takes two BigIntegerss.
The in operator is declared inverse. You need an extension function on the right side that takes the left side.
https://kotlinlang.org/docs/reference/operator-overloading.html#in
You miss an infix function divBy to allow turing Int into Rational, e.g.
infix fun Int.divBy(i: Int) = Rational("$this/$i")
Not the code like val half = 1 divBy 2 will work. Theoretically, it may make sense to add a constructor for Rational from Ints to avoid parsing.
There is an incorrect return type in rangeTo method in the Rational class, it should not be Any. It should be declared as
data class RationalRange(val left: Rational, val right: Rational) {
operator fun contains(r: Rational) = left <= r && r <= right
}
operator fun rangeTo(end: Rational): RationalRange(this, end)
Now the example with x in a..b should work.
UPD: added the RationalRange. I missed the point, sorry. You do not need contains function implemented for the Rational class at all.
The compareTo function of Rational is unlikely to use .toFloat() instead, you may implement that directly with integer numbers
A straightforward solution is to implement the Comparable Interface in your class.
data class Rational(val rational: String) : Comparable<Rational>
Then implement the compareTo() function, with your comparison logic.
override fun compareTo(other: Rational): Int {
//Normalize the numerators of each rational
val thisNumerator = this.numerator * other.denominator
val otherNumerator = other.numerator * this.denominator
//Then compare them
return when{
thisNumerator > otherNumerator -> 1
thisNumerator < otherNumerator -> -1
else -> 0
}
}
This will resolve the compile error without you needing to override the rangeTo() function with custom logic.

List<List<Char>> + List<Char> = List<Any>?

I have a below code which works.
class ListManipulate(val list: List<Char>, val blockCount: Int) {
val result: MutableList<List<Char>> = mutableListOf()
fun permute(sequence: List<Int> = emptyList(), start: Int = 0, count: Int = blockCount) {
if (count == 0) {
result.add(constructSequence(sequence))
return
}
for (i in start .. list.size - count) {
permute(sequence + i, i + 1, count - 1)
}
}
private fun constructSequence(sequence: List<Int>): List<Char> {
var result = emptyList<Char>()
for (i in sequence) {
result += list[i]
}
return result
}
}
However, when I change the result from MutableList to normal List, i.e.
var result: List<List<Char>> = emptyList()
// ...
result += constructSequence(sequence)
I got this error Type mismatch. Require: List<List<Char>>; Found: List<Any>
The full code as below
class ListManipulate(val list: List<Char>, val blockCount: Int) {
var result: List<List<Char>> = emptyList()
fun permute(sequence: List<Int> = emptyList(), start: Int = 0, count: Int = blockCount) {
if (count == 0) {
result += constructSequence(sequence)
return
}
for (i in start .. list.size - count) {
permute(sequence + i, i + 1, count - 1)
}
}
private fun constructSequence(sequence: List<Int>): List<Char> {
var result = emptyList<Char>()
for (i in sequence) {
result += list[i]
}
return result
}
}
Why result + constructSequence(sequence) would result in List<Any> instead of List<List<Char>>?
Is there a way I could still use the normal List> and not the mutable list?
CTRL + click on the + in IDEA, you'll see that it takes you to the following function:
/**
* Returns a list containing all elements of the original collection and then all elements of the given [elements] collection.
*/
public operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T> {
/* ... */
}
Which means that you add all the individual elements of elements to the receiver. That is, you'll add all T's to the List<List<T>>. Since List<T> is not T, you'll get List<Any> as a result.
The problem is that += is overloaded. If it sees an Iterable, Array or Sequence it behaves differently. You have to explicitly use plusElement() to achieve the behaviour you intend.
Consider the following code.:
class ListManipulate(val list: List<Char>, val blockCount: Int) {
var result: List<List<Char>> = emptyList()
fun permute(sequence: List<Int> = emptyList(), start: Int = 0, count: Int = blockCount) {
if (count == 0) {
result = result.plusElement(constructSequence(sequence))
return
}
for (i in start..list.size - count) {
permute(sequence + i, i + 1, count - 1)
}
}
private fun constructSequence(sequence: List<Int>): List<Char> =
List(sequence.size, { i -> list[sequence[i]] })
}
PS: I also took the liberty to update your constructSequence() to something more concise.
Btw: += uses addAll internally.
/**
* Returns a list containing all elements of the original collection and then all elements of the given [elements] collection.
*/
public operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T> {
if (elements is Collection) {
val result = ArrayList<T>(this.size + elements.size)
result.addAll(this)
result.addAll(elements)
return result
} else {
val result = ArrayList<T>(this)
result.addAll(elements)
return result
}
}
Side note: you can also do:
result.toMutableList().add(constructSequence(sequence))
It is fine to return a MutableList, the only difference really is that the List interface doesnt have the manipulation methods. Internally both are represented by an ArrayList
#SinceKotlin("1.1")
#kotlin.internal.InlineOnly
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)