Reputation: 12352
I am trying to detect the differences between two maps in kotlin.
I have setup the below sample to make it easier to explain what I am trying to achieve:
fun main() = runBlocking {
val firstMapOfAnimals = mapOf(
Pair("key1", Dog(name = "Dog aaa")),
Pair("key2", Dog(name = "Dog bbb", breed = "Bulldog")),
Pair("key4", Cat(name = "Cat ddd", color = "White")),
Pair("key5", Cat(name = "Cat eee", color = "Blue")),
Pair("key6", Cat(name = "Cat fff", color = "Blue"))
)
val secondMapOfAnimals = mapOf(
Pair("key2", Dog(name = "Dog BBB")),
Pair("key3", Dog(name = "Dog CCC")),
Pair("key4", Cat(name = "Cat DDD", color = "Grey")),
Pair("key6", Dog(name = "Dog FFF", breed = "Husky"))
)
val diffResult = diff(firstMapOfAnimals, secondMapOfAnimals)
val expectedResultMap = mapOf(
Pair("key2", Dog(name = "Dog BBB", breed = "Bulldog")),
Pair("key3", Dog(name = "Dog CCC")),
Pair("key4", Cat(name = "Cat DDD", color = "Grey")),
Pair("key6", Dog(name = "Dog FFF", breed = "Husky"))
)
println("Actual: $diffResult")
println("Expected: $expectedResultMap")
}
private fun diff(
firstMap: Map<String, Animal>,
secondMap: Map<String, Animal>
): Map<String, Animal> {
val result = mapOf<String, Animal>()
//TODO: get differences between firstMap and secondMap
return result
}
abstract class Animal
data class Dog(
val name: String,
val breed: String = "breed"
) : Animal()
data class Cat(
val name: String,
val color: String = "black"
) : Animal()
My real code is a bit more complex but I want to start simple.
Basically, I need to write the diff()
method body to achieve the expected printed result.
Currently, this is the output:
Actual: {}
Expected: {key2=Dog(name=Dog BBB, breed=Bulldog), key3=Dog(name=Dog CCC, breed=breed), key4=Cat(name=Cat DDD, color=Grey), key6=Dog(name=Dog FFF, breed=Husky)}
I believe that this can be solved with a combination of operators, but due to my still limited knowledge of kotlin, I'm not sure how I can achieve that...
Can someone point me in some direction?
Upvotes: 1
Views: 2794
Reputation: 7892
You want rather specific diff. Will try to formalize it based on provided example:
The first 4 steps could be done like this:
secondMap.map { (key, secondValue) ->
val firstValue = firstMap[key] ?: return@map key to secondValue
if (firstValue == secondValue) return@map null //will filter out them later
if (firstValue.javaClass != secondValue.javaClass) key to secondValue
else key to animalDiff(firstValue, secondValue)
}.filterNotNull().toMap()
But the 5-th step (embedded in animalDiff
function) is rather tricky. The problem is that there is no way to get default value of argument in Kotlin even via reflection (cause it may be not just a compile-time constant, but any arbitrary expression, including function call(s)).
You may do the following trick: add default values for all properties of all subclasses of Animal
class, so that they could be constructed via no-arg reflection method; afterwards default values could be determined from this dummy instance.
data class Dog(
val name: String = "",
val breed: String = "breed"
) : Animal()
data class Cat(
val name: String = "",
val color: String = "black"
) : Animal()
private fun <T : Animal> animalDiff(first: T, second: T): T {
val clazz: KClass<T> = first::class as KClass<T>
val dummyInstance = clazz.createInstance()
val constructor = clazz.primaryConstructor!!
val constructorArgs = clazz.memberProperties.associate { prop: KProperty1<T, *> ->
val defaultValue = prop.get(dummyInstance)
val firstPropValue = prop.get(first)
val secondPropValue = prop.get(second)
val resultPropValue = when {
// secondPropValue == firstPropValue -> defaultValue //uncomment, if it makes sense for your diff logic
secondPropValue != defaultValue -> secondPropValue
firstPropValue != defaultValue -> firstPropValue
else -> defaultValue
}
//Need to convert KProperty into KParameter to pass it into constructor
//Should work fine for data classes
val constructorParam = constructor.parameters.find { it.name == prop.name }!!
return@associate constructorParam to resultPropValue
}
return constructor.callBy(constructorArgs)
}
Upvotes: 0
Reputation: 25873
You can use existing minus()
operator extension function:
secondMapOfAnimals.minus(firstMapOfAnimals)
Or more concisely:
secondMapOfAnimals - firstMapOfAnimals
Also note you can use to()
infix extension function to create Pairs:
"key1" to Dog(name = "Dog aaa")
instead of
Pair("key1", Dog(name = "Dog aaa"))
Upvotes: 5