【问题标题】:Get differences between 2 maps in kotlin获取 kotlin 中 2 个地图之间的差异
【发布时间】:2021-02-03 16:15:02
【问题描述】:

我正在尝试检测 kotlin 中两张地图之间的差异。

我已经设置了以下示例,以便更容易地解释我想要实现的目标:

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()

我的真实代码有点复杂,但我想从简单开始。

基本上,我需要编写diff() 方法体来达到预期的打印结果。 目前,这是输出:

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)}

我相信这可以通过组合运算符来解决,但由于我对 kotlin 的了解仍然有限,我不确定如何实现这一点......

有人能指点我一下吗?

【问题讨论】:

  • 你想要symmetric difference - 即所有那些在第一个地图中但不是第二个在第二个但不是第一个地图的项目?或者你只是想要those in the first but not the second? (反之亦然?)
  • 相等条目的预期差异结果是什么?对于相等的属性,是否有默认值?如果没有默认值,对于相等的属性? val firstMapOfAnimals = mapOf(Pair("key1", Dog(name = "Dog aaa")), Pair("key2", Dog(name = "Dog bbb", breed = "Bulldog")), Pair("key3", Dog(name = "Dog CCC", breed = "Bulldog")))val secondMapOfAnimals = mapOf(Pair("key1", Dog(name = "Dog aaa")), Pair("key2", Dog(name = "Dog BBB", breed = "Bulldog")), Pair("key3", Dog(name = "Dog CCC")))?

标签: dictionary kotlin


【解决方案1】:

您可以使用现有的minus() 运算符扩展功能:

secondMapOfAnimals.minus(firstMapOfAnimals)

或者更简洁:

secondMapOfAnimals - firstMapOfAnimals

另请注意,您可以使用to() 中缀扩展函数来创建 Pairs:

"key1" to Dog(name = "Dog aaa")

而不是

Pair("key1", Dog(name = "Dog aaa"))

【讨论】:

    【解决方案2】:

    你想要相当具体的差异。 将尝试根据提供的示例对其进行形式化:

    1. 如果第一个映射中存在键,但第二个映射中没有,则将其丢弃
    2. 如果键存在于第二个映射中,但不存在于第一个映射中,则会保留它
    3. 如果两个映射中都存在键且值相等,则将其丢弃
    4. 如果两个映射中都存在键,但值的类型不同,则第二个映射中的值优先
    5. 如果两个映射中都存在键并且值属于同一类型,则生成的差异值应包含不同的属性
      • 属性的非默认值应优先
      • 如果两个属性的值都不是默认值,则第二个优先((?) 即使它们相等)

    前 4 个步骤可以这样完成:

    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()
    

    但是第 5 步(嵌入在animalDiff 函数中)相当棘手。问题是 Kotlin 中的 there is no way to get default value of argument 甚至通过反射(因为它可能不仅仅是编译时常量,而是任何任意表达式,包括函数调用)。

    你可以做以下技巧:为Animal类的所有子类的所有属性添加默认值,以便它们可以通过no-arg reflection method构造;之后可以从这个虚拟实例中确定默认值。

    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)
    }
    

    【讨论】:

      猜你喜欢
      • 2021-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-04
      相关资源
      最近更新 更多