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.
Related
I have made an extension functions for BigIntegers, allowing me to add Ints to them.
operator fun BigInteger.plus(other: Int): BigInteger = this + other.toBigInteger()
// Allowing me to do
val c = myBigInt + 3
I have also made a Counter class, holding bigintegers for various keys, for easy counting. Since doing counter["1"] += myBigInt isn't allowed on standard maps (it's nullable), I have added a custom getter that returns a default value, making this possible.
class Counter<K>(val map: MutableMap<K, BigInteger>) : MutableMap<K, BigInteger> by map {
constructor() : this(mutableMapOf())
override operator fun get(key: K): BigInteger {
return map.getOrDefault(key, BigInteger.ZERO)
}
I can then use it like this
val counter = Counter<String>()
c["ones"] += 5.toBigInteger()
Problem is that I cannot use it like this:
c["ones"] += 5 // doesn't work, "Kotlin: No set method providing array access"
but this should be equivalent to this, which works, since it should use my extension operator on the bigint:
c["ones"] = c["ones"] + 5 // works
Why doesn't this work?
I've tried adding a set method for Ints, but then I see a very weird behavior. Kotlin will do the calculation correct, but then convert the BigInteger to an Int before passing it to my class! Example:
inline operator fun BigInteger.plus(other: Int): BigInteger {
val bigInteger = this + other.toBigInteger()
println("calculated bigint to $bigInteger")
return bigInteger
}
class Counter<K>(val map: MutableMap<K, BigInteger>) : MutableMap<K, BigInteger> by map {
constructor() : this(mutableMapOf())
override operator fun get(key: K): BigInteger {
return map.getOrDefault(key, BigInteger.ZERO)
}
operator fun set(key: K, value: Int) {
println("setting int $value")
map[key] = value.toBigInteger()
}
}
val c = Counter<String>()
c["1"] = "2192039569601".toBigInteger()
c["1"] += 5
println("result: ${c["1"]}")
c["1"] = "2192039569601".toBigInteger()
c["1"] = c["1"] + 5
println("result: ${c["1"]}")
Which prints
calculated bigint to 2192039569606
setting int 1606248646 <--- why does it call the int setter here?
result: 1606248646
calculated bigint to 2192039569606
result: 2192039569606
Why does Kotlin do the BigInt summation, but converts it back to an Int before sending to my setter?
Update
Since a comment suggest this is a compiler issue, any other ideas?
My ultimate goal here, was to have a counter of big integers, but to be able to easily add ints to it.
Adding this as a set function, makes it being called for both ints and bigints, so I can do the proper assignment myself. However, it will also then allow someone to add floats that will crash at runtime.
operator fun set(key: K, value: Number) {
map[key] = when (value) {
is BigInteger -> value
is Int -> value.toBigInteger()
else -> throw RuntimeException("only ints")
}
}
Any tips?
Notice that c["ones"] += 5 can be translated into calls in two ways:
c.set("ones", c.get("ones").plus(5))
c.get("ones").plusAssign(5)
The first way is what your code currently translates to, because you don't have a plusAssign operator defined. As I said in the comments, there is a bug in the compiler that prevents the operators from resolved correctly. When resolving c["ones"] += 5, It seems to be trying to find a set operator that takes an Int instead (possibly because 5 is an Int), which is unexpected. If you modify the code in the bug report a little, you can even make it throw an exception when executed!
class Foo {
operator fun get(i: Int) : A = A()
operator fun set(i: Int, a: A) {}
operator fun set(i: Int, a: Int) {}
}
class A {
operator fun plus(b: Int) = A()
}
class B
fun main(args: Array<String>) {
val foo = Foo()
foo[0] = foo[0] + 1
foo[0] += 1 // this compiles now, since there is a set(Int, Int) method
// but A can't be casted to Int, so ClassCastException!
}
It is rather coincidental (and lucky) in your case, that the compiler knows how to convert from BigInteger (or any other Number type actually) to Int, using Number#intValue. Otherwise the program would have crashed too.
A natural alternative way is to define the plusAssign operator, so that the assignment gets translated the second way. However, we can't do it on BigInteger, because plusAssign would need to mutate this, but BigInteger is immutable. This means that we need to create our own mutable wrapper. This does mean that you lose the nice immutability, but this is all I can think of.
fun main() {
val c = Counter<String>()
c.set("1", "2192039569601".toMutableBigInteger())
c.get("1").plusAssign(5)
println("result: ${c["1"]}")
}
data class MutableBigInteger(var bigInt: BigInteger) {
operator fun plusAssign(other: Int) {
bigInt += other.toBigInteger()
}
}
fun String.toMutableBigInteger() = MutableBigInteger(toBigInteger())
class Counter<K>(val map: MutableMap<K, MutableBigInteger>) : MutableMap<K, MutableBigInteger> by map{
constructor() : this(mutableMapOf())
override operator fun get(key: K): MutableBigInteger {
return map.getOrPut(key) { MutableBigInteger(BigInteger.ZERO) }
}
operator fun set(key: K, value: Int) {
println("setting int $value")
map[key] = MutableBigInteger(value.toBigInteger())
}
}
Notably, getOrDefault is changed to getOrPut - when a value is not found, we want to put the zero we return into the map, rather than just returning a zero that is not in the map. Our changes to that instance wouldn't be visible through the map otherwise.
I made a function which returns all factors of an integer. But the problem is that it gives an error that the return type is Unit instead of mutableListOf. my code:
fun get_factors(num: Int) {
var factors = mutableListOf<Int>()
for (x in 1..num) {
if (x % num == 0) {
factors.add(x)
}
}
return factors
}
fun main() {
print(get_factors(18))
}
I tried doing:
fun get_factors(num: Int): mustableListOf {
var factors = mutableListOf<Int>()
for (x in 1..num) {
if (x % num == 0) {
factors.add(x)
}
}
return factors
}
fun main() {
print(get_factors(18))
}
but it says mutableListOf not defined.
I just started learning Kotlin today so I am a total beginner with Kotlin. Please help me out with this.
You seem to have confused the type MutableList<Int>, with the function mutableListOf.
Since the type name is MutableList<Int>, you should do:
fun get_factors(num: Int) : MutableList<Int> {
...
}
Or, if the caller doesn't need to modify the list, you can just return List<Int> too:
fun get_factors(num: Int) : List<Int> {
...
}
I can see why this is confusing - to create an instance of a type, you normally just add () to the end of the type name, so if mutableListOf() creates a list, you'd think that mutableListOf is the type name. However, this is actually calling the global function called mutableListOf, which returns an instance of MutableList<T>.
One way to distinguish between these is to look at the first letter. Type names usually begin with a capital letter, whereas function names begin with a small letter.
In next code I'm trying to write DSL, which allowed some constructions for hands of some robot.
It should have syntax below, which I should use in outer context for RobotBuilder class:
robot {
hands {
plastik width 3
load = light - medium
}
head {
...
}
etc...
}
The problem is in string load = light - medium. It's do nothing, but Robot created. What I do? Just overload minus operator for Int. For me when Kotlin compiler see expression of = - it should use overloaded operator. What I am doing wrong?
Should I create special class like LoadClassBuilder with only one attribute of type LoadClass and then overload minus operator in HandsBuilder for - and change types for load, light and medium or problem in outer context of my DSL?
class HandsBuilder {
var material: Material = Metal(0)
var minLoad: LoadClass = LoadClass.Medium
var maxLoad: LoadClass = LoadClass.Medium
val plastik: Plastik = Plastik(0)
val metal: Metal = Metal(0)
infix fun Metal.width(width: Int) {
material = Metal(width)
}
infix fun Plastik.width(width: Int) {
material = Plastik(width)
}
var load: Int = 0
val light: Int = 1
val medium: Int = 2
operator fun Int.minus(other: Int): Int {
println("minus call")
minLoad = LoadClass.values()[this]
println(this)
maxLoad = LoadClass.values()[other]
println(other)
return 1
}
fun build() = Hands(material, minLoad, maxLoad)
}
Update 1:
fun hands(init: HandsBuilder.() -> Unit) {
handsParamInRobotBuilderClass = HandsBuilder().apply(init).build()
}
I am new in learning Kotlin. I try to using tail recursion for calculating factorial in my code, but when I enter 99999, the output is 0. Here is my code:
fun main(){
println(factTailRec(99999))
}
tailrec fun factTailRec(number: Int, result: Int = 1): Long{
return if(number.toInt() == 1){
result.toLong()
} else {
factTailRec(number-1, result*number)
}
}
Your result of type Int is not sufficient to hold the actual resulting value. You need something like BigInteger:
import java.math.BigInteger
import java.math.BigInteger.ONE
inline operator fun BigInteger.times(other: Int): BigInteger =
this.multiply(BigInteger.valueOf(other.toLong()))
tailrec fun factTailRec(number: Int, result: BigInteger = ONE): BigInteger {
return if (number == 1) {
result
} else {
factTailRec(number - 1, result * number)
}
}
fun main() {
println(factTailRec(99999)) // 28242294079...
}
In Kotlin, it is possible to write
class A {
fun B.foo()
}
and then e.g. write with (myA) { myB.foo() }.
Is it possible to write this as an extension method on A, instead? My use case is writing
with (java.math.RoundingMode.CEILING) { 1 / 2 }
which I would want to return 1, the point being that I want to add operator fun Int.div(Int) to RoundingMode.
No it's not possible. operator div is required to have Int as a receiver.
You can't add also RoundingMode as receiver, since there can only be single function receiver.
What you can do, though, is use Pair<RoundingMode, Int> as a receiver:
operator fun Pair<RoundingMode, Int>.div(i: Int): BigDecimal =
BigDecimal.valueOf(second.toLong()).divide(BigDecimal.valueOf(i.toLong()), first)
with(RoundingMode.CEILING) {
println((this to 1) / 2) // => 1
}
That's not possible, Int already has a div function, thus, if you decide to write an extension function div, you won't be able to apply it, because member functions win over extension functions.
You can write this though:
fun RoundingMode.div(x: Int, y: Int): Int {
return if (this == RoundingMode.CEILING) {
Math.ceil(x.toDouble() / y.toDouble()).toInt()
} else {
Math.floor(x.toDouble() / y.toDouble()).toInt()
}
}
fun main(args: Array<String>) {
with(java.math.RoundingMode.CEILING) {
println(div(1,2))
}
}
It's not possible for a couple of reasons:
There's no "double extension functions" concept in Kotlin
You can't override a method with extension functions, and operator div is already defined in Int
However you can workaround these issues with
A context class and an extension lambda (e.g. block: ContextClass.() -> Unit)
Infix functions (e.g. use 15 div 4 instead of 15 / 4)
See the example below:
class RoundingContext(private val roundingMode: RoundingMode) {
infix fun Int.div(b: Int): Int {
val x = this.toBigDecimal()
val y = b.toBigDecimal()
val res = x.divide(y, roundingMode)
return res.toInt()
}
}
fun <T> using(roundingMode: RoundingMode, block: RoundingContext.() -> T): T {
return with(RoundingContext(roundingMode)) {
block()
}
}
// Test
fun main(args: Array<String>) {
using(RoundingMode.FLOOR) {
println(5 div 2) // 2
}
val x = using(RoundingMode.CEILING) {
10 div 3
}
println(x) // 4
}
Hope it helps!