How to store a temporary variable when initializing a Kotlin object?

I am studying Kotlin and as part of the training, I want to create a class that will represent a rational number, the requirements are:

  • The class must contain two immutable integer fields: the numerator and the denominator.
  • The class must contain valid equals, hashCode, and toString values.
  • When the class is initialized, the numerator and denominator must be removed from their GCD (meaning that Ratio(1, 2) == Ratio(2, 4 /* or 4, 8 */) or Ratio(2, 4 /* or 4, 8 */).numerator == 1, .denominator == 2

    , etc.)
  • This class must contain a mul method that takes another relation and returns the multiplication of the current relation and the given one.

I tried to use data classes that looked appropriate for the task, but I got stuck in not being able to define a custom constructor (both the numerator and denominator need to be removed in their GCD).

Possible Solution:

class Ratio(num : Int, denom : Int) {
    val numerator = num / gcd(num, denom)
    val denominator = denom / gcd(num, denom) // GCD calculated twice!
}

      

What is the simplest way to define a constructor for a class so that the GCD is evaluated once?

UPDATE

OK, it looks like I found a possible solution:

data class Ratio(num : Int, denom : Int) {
  val numerator : Int
  val denominator : Int

  {
    val gcd = calcGcd(num, denom)
    numerator = num / gcd
    denominator = denom / gcd
  }
}

      

but it renders this data attribute useless - after this change, the Ratio class no longer has auto-generated equals / hashCode / toString.

Tested on the latest Kotlin version - 0.9.66

A program that reproduces this behavior:

data class Ratio(num : Int, denom : Int) {
  val numerator : Int
  val denominator : Int

  {
    val gcd = BigInteger.valueOf(num.toLong()).gcd(BigInteger.valueOf(denom.toLong())).intValue();
    numerator = num / gcd;
    denominator = denom / gcd
  }
}

data class Ratio2(val num : Int, val denom : Int)

fun main(args: Array<String>) {
  println("r = " + Ratio(1, 6).toString())
  println("r2 = " + Ratio2(1, 6).toString())
}

      

output:

r = Ratio@4ac68d3e
r2 = Ratio2(num=1, denom=6)

      

which is clear that Ratio no longer has an auto generated toString method

+3


source to share


2 answers


Ok, I found the answer (thanks to Andrew for pointing out the need to have a private ctor in the described use case):

data class Ratio private (val numerator : Int, val denominator : Int) {
  class object {
    fun create(numerator : Int, denominator : Int) : Ratio {
      val gcd = BigInteger.valueOf(numerator.toLong()).gcd(BigInteger.valueOf(denominator.toLong())).intValue();
      return Ratio(numerator / gcd, denominator / gcd)
    }
  }
}

      



for some reason, the 'data' qualifier will be useless if the class uses initializer blocks, so if you want custom build logic and keep the automatically generated hashCode / equals / toString methods, you will need to use a factory.

+3


source


What about:

class Ratio(num : Int, denom : Int) {
 private val theGcd = gcd(num, denom)
 val numerator = num / theGcd
 val denominator = denom / theGcd
}

      

EDIT: Fair point regarding the useless area. An alternative could be to use a lazy evaluated object. See the docs here http://kotlinlang.org/docs/reference/delegated-properties.html



Here (unverified) goes for it.

import kotlin.properties.Delegates

class Ratio(num : Int, denom : Int) {
 private val theGcd: Int by Delegates.lazy {
    gcd(num, denom) 
 }

 val numerator = num / theGcd
 val denominator = denom / theGcd
}

      

0


source







All Articles