I'm trying to compare 2 Maps, both are filled with identical Strings (just for training).
When I try to compare them I got false returned through map.equals(map2)
But if I do the same and add .toString() to the comparison it returns true. map.toString().equals(map2.toString())
How's that? What am I doing wrong?
Also happens the same with Kluent library and its shouldEqual method.
I also tried to get values of that maps into arrays and tried to compare that arrays but it still returns false.
Also, when I print all items from each map I see the same 3 Strings but comparison returns fail.
Here's the code:
class MapsTesting {
class Items(name1: String, amount1: String, price1: String) {
var name: String = name1
var amount: String = amount1
var price: String = price1
override fun toString(): String {
return "name:$name,amount:$amount,price:$price \n"
}
}
#Test
fun mapTest() {
val mapOfItems: MutableMap<String, Items> = mutableMapOf()
mapOfItems["Materials"] = Items("STEP 1", "STEP 2", "View All Stones")
val mapOfItems2: MutableMap<String, Items> = mutableMapOf()
mapOfItems2["Materials"] = Items("STEP 1", "STEP 2", "View All Stones")
mapOfItems.forEach { t, u ->
println("map key:$t,map value:${u.toString()}")
}
mapOfItems2.forEach { t, u ->
println("map key:$t,map value:${u.toString()}")
}
val comparison : Boolean = mapOfItems.toString().equals(mapOfItems2.toString())
//this returns true
val comparison2 : Boolean = mapOfItems.equals(mapOfItems2)
//this returns false
println(comparison)
println(comparison2)
val map1 = mapOfItems.toString()
val map2 = mapOfItems2.toString()
//this returns true
//if I delete toString() it will return false
map1 shouldEqual map2
}
}
Here's the error I got:
java.lang.AssertionError: expected: java.util.LinkedHashMap<{Materials=name:STEP 1,amount:STEP 2,price:View All Stones
}> but was: java.util.LinkedHashMap<{Materials=name:STEP 1,amount:STEP 2,price:View All Stones
}>
When comparing two maps with the equals method, the standard implementation compares all keys and values also using the equals method.
Your Items class uses the standard equals implementation (two reference values are equal if and only if they refer to the same object). But you clearly have two different instances of the in different maps.
You either have to override the Items#equals method or mark the Items class as a data class:
data class Items(var name1: String, var amount1: String, var price1: String)
Kotlin will generate correct equals, hashCode and toString methods.
After this the map comparison will work fine.
Related
I receive data from Request information as list data (List) below code. That data has a "key" parameter by which I want to sort it.
data class ApplianceSetting(
#SerializedName("key") val key: String,
#SerializedName("value") var value: Any,
(...)
I have the required order in the SettingsUtilEnum and want to sort items by that.
After that, I can convert the list using map{} the data and use the function of Enum getSettingByMode() and get the list of Enum values. Then I will sort them and convert them again to List.
But that sounds too inefficient. Is there a better way.
enum class SettingsUtilEnum(
var settingKey: String,
override val order: Int = 99,
var settingName: String = "",
) : AbstractOrderEnum {
FIRST_MODE("first.mode", 0),
SECOND_MODE("second.mode", 1),
(...)
UNKNOWN_MODE("", 99);
companion object {
#JvmStatic
fun getSettingByMode(settingKey: String): SettingsUtilEnum? {
return values().find { it.settingKey == settingKey }
}
k
private fun initDataObserver() {
(activity as FavouriteActivity).viewModel.applianceSettings.observe(activity as FavouriteActivity
) { data ->
(controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
val adapter = (controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
// public final var data: List<ApplianceSetting>
// old code:
// data.settings
adapter.data = sortAndGetControlModes(data)
adapter.notifyDataSetChanged()
}
}
// TODO: sortAndGetControlModes
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.map {
getSettingByMode(it.key)
?: UNKNOWN_MODE.apply {
// If in future new modes are added -> put them as tail
settingKey = it.key
}
}.sortedBy { it.order }
// error i need to return again List<ApplianceSetting>
If you want to compare keys with theirs ASCII values you can just use sortBy { it.key }
If you want to expand possibilities of comparison you can use function sortedWith with passing custom comparator as argument.
Comparator used to compare its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
Example:
You can use it like that if you want to sort by integer value of key parameter:
data.settings.sortedWith { a, b ->
when {
a.key.toInt() < b.key.toInt() -> -1
a.key.toInt() > b.key.toInt() -> 1
else -> 0
}
}
I fixed it using sortedBy and as comparator I am using received value (order) from getSettingByMode(), if item is not found (null) I give him order value of 99 and put it on tail position:
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.sortedBy {
getSettingByMode(it.key)?.order ?:99
}
I'm confused about the different behaviour depending whether I use getters or delegated properties. Consider the following:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH GETTERS:
val partA get() = parts.a
val partB get() = parts.b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
This code splits the string raw into two parts the first time parts is accessed. All later calls to parts will return the cached parts, even if raw changes. Output:
#1
#2
#2
a=one, b=two
The value of raw is empty when Test is created, but the accessors aren't called until we've set raw to some string. When partA and partB are finally accessed, they contain the correct value.
If I use property delegation instead, the code no longer works:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("#2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH DELEGATION:
val partA by parts::a
val partB by parts::b
}
fun main() {
val t = Test()
println("#1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
All I've changed here is that partA is now delegated to parts::a, and the same for partB. For some strange reason, partA and partB are now accessed before the value of raw is set, so cachedParts is initilized with two empty parts. Output:
#2
#2
#1
a=, b=
Can someone explain what is going on here?
See what your delegated properties translate to in the documentation here. For example, partA translates to:
private val partADelegate = parts::a
val partA: String
get() = partADelegate.getValue(this, this::partA)
Notice that the callable reference expression part::a is used to initialise partADelegate. This expression is evaluated when the instance of Test is created, before println("#1").
To evaluate parts::a, parts must be first evaluated. After all, this is a reference to the a property of parts, not a reference to parts.
Therefore, parts ends up being evaluated before raw gets its value.
In Kotlin, is it possible to overload the following operation to x: X?
x[i] += j
Currently, I can only see an indirect way, like defining some X.get that returns an object of type XAtIndex with a reference to the original, then defining XAtIndex.plugAssign that modifies the original.
If you want a += that mutates an object instead of changing what is stored in a variable, you must implement plusAssign on the type of the object returned by get(). An example of this in the standard library is MutableList.plusAssign().
If you want the more traditional behavior of reassigning the value held at an index after creating a modified copy, your class should have matching get and set operator functions. Then you can implement a plus function on whatever is the type of the get/set (if it doesn't have one). When += is used, it will use the getter and setter operator functions along with the plus operator of the type returned by get. Example:
class Foo {
private var thing1: String = "Hello"
private var thing2: String = "World"
operator fun get(thing: Int) = when (thing) {
1 -> thing1
2 -> thing2
else -> throw IllegalArgumentException()
}.also { println("get") }
operator fun set(thing: Int, value: String) {
when (thing) {
1 -> thing1 = value
2 -> thing2 = value
else -> throw IllegalArgumentException()
}
println("set")
}
override fun toString(): String ="Foo(thing1='$thing1', thing2='$thing2')"
}
fun main() {
val foo = Foo()
foo[1] += "!!!"
println(foo)
}
how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...
Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)