Different Property setter for three same class objects in Kotlin - kotlin

What I am trying to implement are three different temperature values depending on the city name.
The following class:
class City(val name: String) {
var degrees: Int = 0
set(value) {
when(this.name){
("Dubai") -> 30
"Moscow" -> 5
"Hanoi" -> 20
}
field = value
}}
And main func:
fun main() {
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
println(firstCity.degrees) // 0
}
Why is it set to default value 0? For Dubai it should have been 30.

The degrees are initialized with 0 and never changed due to no invocation of the setter, which lacks a value for cities that are not expected (maybe that's why you initialized the degrees?).
You could do what you want way shorter:
class City(val name: String) {
var degrees: Int = when(name) {
"Dubai" -> 30
"Moscow" -> 5
"Hanoi" -> 20
else -> 0 // default value for unpredictable cities
}
}
fun main() {
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
println(firstCity.degrees)
}
This will output 30

Related

Re-initialize object properties in Kotlin

I often have a class with properties that are initialized at instantiation, like
class C {
var x = 10
var y = v * 2 // v is some variable
}
val c = C()
then properties of c are changed, and later I need to re-initialize the properties (so that c.x is 10 again and c.y is v*2, where the value of v may have changed).
My current approach is to initialize the properties with dummy values (or alternatively use lateinit and type annotations), and assign the desired values in an extra function ini like
class C {
var x = 0
var y = 0
init {
ini()
}
fun ini() {
x = 10
y = v * 2
}
}
then I call c.ini() to re-initialize.
Is there a better (more succinct) way that avoids the dummy values?
Note that in JavaScript I can simply write
class C {
constructor() {
this.ini()
}
ini() {
this.x = 10
this.y = v * 2
}
}
What you are doing right now is totally fine. I wouldn't change it.
If you want to avoid the placeholder values of 0, or if the type of the property has no placeholder value that make sense, you can store the initial values in private lambdas:
class C {
private val xInit = { 10 }
private val yInit = { v * 2 }
var x = xInit()
var y = yInit()
fun reset() {
x = xInit()
y = yInit()
}
}
If you really want to reduce the boilerplate, I can only think of this rather hacky and slow solution that relies on reflection. This could be usable if the instances don't need to be reset that often, and you have lots of these classes with resettable properties, and you just hate writing boilerplate reset methods.
class Resettable<T>(val supplier: () -> T) {
var wrapped = supplier()
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return wrapped
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
wrapped = value
}
fun reset() {
wrapped = supplier()
}
}
fun <T: Any> T.reset() {
// look for properties delegated with Resettable
this::class.memberProperties.mapNotNull {
it.isAccessible = true
(it as KProperty1<T, *>).getDelegate(this)
}.filterIsInstance<Resettable<*>>().forEach {
it.reset()
}
}
Now you just need to do:
var v = 10
class C {
var x by Resettable { 10 }
var y by Resettable { v * 2 }
}
fun main() {
val x = C()
println(x.y) // 20
v = 20
x.reset()
println(x.y) // 40
v = 40
x.reset()
println(x.y) // 80
}
Note that the first reset call would load all the kotlin reflection classes, and would take a noticeable amount of time.

Why the setter function is not running in this Kotlin program

The following is the kotlin program. I have confusion that why the setter function is not being called on running line x.b=20.
class Num(value: Int) {
var a = value + 4
var b = value + 6
var c = value + 1
set(value) {
println("field is : ${field}")
field = value + b
println("field is : ${field}")
println("inside setter am here")
}
}
fun main(args: Array<String>) {
var x = Num(3)
x.b = 20
println(x.b)
}
It would be much clearer if your indentation were correct:
class Num(value: Int) {
var a = value + 4
var b = value + 6
var c = value + 1
set(value) {
println("field is : ${field}")
field = value + b
println("field is : ${field}")
println("inside setter am here")
}
}
You have only defined a setter on c. There is no way to make one setter for all the values, though factoring out a method might help.
Louis explained why it's not working, but this statement:
There is no way to make one setter for all the values
is not correct. It is possible by using delegated properties:
import kotlin.reflect.KProperty
class Num(value: Int) {
var a by MySetter(value + 4)
var b by MySetter(value + 6)
var c by MySetter(value + 1)
}
class MySetter(private var field) {
operator fun getValue(thisRef: Num, property: KProperty<*>) = field
operator fun setValue(thisRef: Num, property: KProperty<*>, value: Int) {
println("field is : $field")
field += value
println("field is : $field")
println("inside setter am here")
}
}
fun main(args: Array<String>) {
var x = Num(3)
x.b = 20
println(x.b)
}
This prints:
field is : 9
field is : 29
inside setter am here
29

Setter not assigning value in Kotlin

I am trying to do a temperature program, which outputs the lowest temperature from three cities provided. If the temperature of one of three cities is above + 57 or below -92 all three cities will have set default values which are (+5 Moscow, +20 Hanoi , 30 for Dubai)
However providing those numbers 20,100,35 in readLine doesn't work.
This is how City class looks like:
class City(val name: String) {
var degrees: Int = 0
set(value) {
field =
if (value > 57 || -92 > value) {
when (this.name) {
"Dubai" -> 30
"Moscow" -> 5
"Hanoi" -> 20
else -> 0
}
} else {
value
}
}}
And in my main I have:
val first = readLine()!!.toInt()
val second = readLine()!!.toInt()
val third = readLine()!!.toInt()
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
firstCity.degrees = first
secondCity.degrees = second
thirdCity.degrees = third
println(first)
println(second)
println(third)
What's wrong in the setter? Why does the second doesn't set the default values?
Works as expected to me https://pl.kotl.in/otINdg8E3:
class City(val name: String) {
var degrees: Int = 0
set(value) {
field =
if (value > 57 || -92 > value) {
when (this.name) {
"Dubai" -> 30
"Moscow" -> 5
"Hanoi" -> 20
else -> 0
}
} else {
value
}
}}
fun main() {
val firstCity = City("Dubai")
val secondCity = City("Moscow")
val thirdCity = City("Hanoi")
firstCity.degrees = 100
secondCity.degrees = -100
thirdCity.degrees = 6
println(firstCity.degrees) // prints 30
println(secondCity.degrees) // prints 5
println(thirdCity.degrees) // prints 6
}

Kotlin - Random.nextInt Range

Aim of code: Shopping system,function which shows a matched product name from the warehouse
what is the no. range of Random.nextInt() if no no. is assigned inside ()?
in fun fillWarehouse, if i do not set no. inside "StockUnit(Random.nextInt(),Random.nextInt())", when i call println("Number of items: ${p.availableItems}") in main, No. -890373473 / 1775292982 etc. were generated.
if i set 100 inside like "StockUnit(Random.nextInt(100),Random.nextInt(100))", No. 263 / 199 etc. were generated. why is it not within 0-100? may i know how to change my code, so that "Number of items" is within 100?
any links or topics should i work for, to write better code?
i cannot find the answers from https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.random/
Sincere thanks!
fun main(args: Array<String>) {
val warehouse = Warehouse()
...
println("Show info")
showInfo(warehouse)
}
fun showInfo(warehouse: Warehouse) {
println("Get Info")
val input = readLine() ?: "-"
val p = warehouse.getProductByName(input)
if (p != null) {
println("Product: $p")
println("Number of items: ${p.availableItems}")
println("Profit: ${p.profitPerItem}")
}
}
class Warehouse {
private val products = mutableListOf<Product>()
...
fun getProductByName (productName: String): Product? {
for (prod in products)
if (prod.productName == productName) return prod
return null
}
fun fillWarehouse (productName: String,
basePrice: Double,
productDescription: String,
chargeOnTop: Double = 50.0,
intialStockUnits: Int = 3) {
val newProduct = Product(productName, basePrice, basePrice * (1 + chargeOnTop / 100), productDescription)
//add quantity, daysBeforeExpiration
for (i in 1 .. intialStockUnits){
val unit = StockUnit(Random.nextInt(),Random.nextInt() )
newProduct.addStock(unit)
}
open class Product(
val productName: String,
var basePrice: Double,
open val salesPrice: Double,
val description: String) {
...
var stockUnits = mutableListOf<StockUnit>()
...
// availableItems = Total of stockUnits
var availableItems: Int = 0
get() = stockUnits.sumBy { it.quantity }
}
class StockUnit(var quantity:Int, var daysBeforeExpiration:Int){
...
}

How do I bind a custom property to a textfield bidirectionally?

I have a complex object that I want to display in a textfield. This is working fine with a stringBinding. But I don't know how to make it two-way so that the textfield is editable.
package com.example.demo.view
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
class MainView : View("Hello TornadoFX") {
val complexThing: Int = 1
val complexProperty = SimpleObjectProperty<Int>(complexThing)
val complexString = complexProperty.stringBinding { complexProperty.toString() }
val plainString = "asdf"
val plainProperty = SimpleStringProperty(plainString)
override val root = vbox {
textfield(complexString)
label(plainProperty)
textfield(plainProperty)
}
}
When I run this, the plainString is editable and I see the label change because the edits are going back into the property.
How can I write a custom handler or what class do I need to use to make the stringBinding be read and write? I looked through a lot of the Property and binding documentation but did not see anything obvious.
Ta-Da
class Point(val x: Int, val y: Int) //You can put properties in constructor
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
if(string.isNullOrBlank()) return null //Empty strings aren't valid
val xy = string.split(",", limit = 2) //Only using 2 coordinate values so max is 2
if(xy.size < 2) return null //Min values is also 2
val x = xy[0].trim().toIntOrNull() //Trim white space, try to convert
val y = xy[1].trim().toIntOrNull()
return if(x == null || y == null) null //If either conversion fails, count as invalid
else Point(x, y)
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6) //Probably doesn't need to be its own member
val pointProperty = SimpleObjectProperty<Point>(point)
val pc = PointConverter()
override val root = vbox {
label(pointProperty, converter = pc) //Avoid extra properties, put converter in construction
textfield(pointProperty, pc)
}
}
I made edits to your converter to "account" for invalid input by just returning null. This is just a simple band-aid solution that doesn't enforce correct input, but it does refuse to put bad values in your property.
This can probably be done more cleanly. I bet there is a way around the extra property. The example is fragile because it doesn't do input checking in the interest of keeping it simple. But it works to demonstrate the solution:
class Point(x: Int, y: Int) {
val x: Int = x
val y: Int = y
}
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
val xy = string?.split(",")
return Point(xy[0].toInt(), xy[1].toInt())
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6)
val pointProperty = SimpleObjectProperty<Point>(point)
val pointDisplayProperty = SimpleStringProperty()
val pointStringProperty = SimpleStringProperty()
val pc = PointConverter()
init {
pointDisplayProperty.set(pc.toString(pointProperty.value))
pointStringProperty.set(pc.toString(pointProperty.value))
pointStringProperty.addListener { observable, oldValue, newValue ->
pointProperty.set(pc.fromString(newValue))
pointDisplayProperty.set(pc.toString(pointProperty.value))
}
}
override val root = vbox {
label(pointDisplayProperty)
textfield(pointStringProperty)
}
}