【问题标题】:Comparing two lists in kotlin比较 kotlin 中的两个列表
【发布时间】:2018-08-28 09:09:07
【问题描述】:

我遇到了 kotlin equals 函数来比较两个相同类型的列表。它适用于带有数据类的纯 Kotlin。

我在 Kotlin 项目中使用 Java 库,其中回调方法在 X 秒的时间间隔内返回对象列表。每次调用都尝试将旧列表与新列表进行比较,但即使项目相同且相等,equals 也会返回 false。

val mOldList: MutableList<MyObject>()? = null

override fun updatedList(list: MutableList<MyObject>){
    // other code
    if (mOldList.equals(list)) // false everytime
}

这是因为 Java 库中的 equals 方法吗?

列表比较的替代建议将不胜感激。

【问题讨论】:

  • 两个列表都是java List?
  • 也许是不同类型的列表?例如ArrayListLinkedList
  • 是的,两者都是 java 列表。它们不是不同的类型;我在 x 秒后将列表与新更新的列表进行比较。
  • 当您说“项目相同且相等”时,您是什么意思? Referential equality or structural equality? IE。 MyObject 会覆盖 equals() 方法吗?
  • 在Java中,“换句话说,如果两个列表包含相同顺序的相同元素,则它们被定义为相等。此定义确保equals方法在List接口的不同实现中正常工作。”所以如果它返回false,肯定有不同的元素。 (并且不同的列表类型应该无关紧要。)

标签: java kotlin kotlin-interop


【解决方案1】:

仅供参考,如果您的自定义对象基于 data class(它会自动覆盖您的 equals),您无需任何额外工作即可调用 list1 == list2

【讨论】:

  • 谢谢。对于对此感到疑惑的人,listOfdataClass1.map { it.string } == listOfdataClass2.map { it.string } 也可以按预期工作。
  • 如果元素的顺序不同,这将不起作用,一个选项是在比较它们之前对两个列表进行排序。
【解决方案2】:

如果您不关心两个列表中元素的顺序,并且您的目标是检查两个列表是否具有完全相同的元素,而没有其他任何元素,您可以考虑两个相互的 containsAll 调用,例如:

var list1 = mutableListOf<String>()
var list2 = mutableListOf<String>()

if(list1.containsAll(list2) && list2.containsAll(list1)) {
    //both lists are of the same elements
}

【讨论】:

  • 我对此表示反对 - 这绝对不是最好的方法。
  • @shabunc 您能否说明这种方式有什么问题?
  • @DruidKuma 因为你基本上调用 containsAll 两次而不是 O(n) 比较,这只是不能比每个索引的线性比较有更好的性能。 - 例如检查这个 - stackoverflow.com/questions/10199772/…
  • 应该使用List,而不是SetHashSet(不是TreeSet - 这很重要)集合具有 O(1) 查找复杂度(列表具有 O(N))。所以只需将mutableListOf 都替换为mutableSetOf
  • 如果您的列表中没有重复,您可以先比较大小,然后一个 containsAll() 就足够了。
【解决方案3】:

Java 列表实现equals 方法,如果两个列表以相同的顺序包含相同的元素,则它们被定义为相等。我猜,您的MyObject 类中缺少equals 方法。

【讨论】:

  • MyObject 是库中的一个类,所以我不能覆盖 equals() 方法。
  • 如果可能,您始终可以扩展MyObject,然后覆盖equals()
  • 是的,很简单。如果 MyObject 是 final 会发生什么?
  • 使用装饰器?
【解决方案4】:

使用zip

zip 返回由该数组的元素和具有相同索引的另一个数组的元素构建的对列表。返回的列表具有最短集合的长度。

fun listsEqual(list1: List<Any>, list2: List<Any>): Boolean {

    if (list1.size != list2.size)
        return false

    val pairList = list1.zip(list2)

    return pairList.all { (elt1, elt2) ->
        elt1 == elt2       
    }
}

【讨论】:

  • 这个方法分配了几个不需要的对象:从最长的列表中,每个元素至少有一个新列表和新的 Pair。此外,分配了几个枚举器,但是 JIT 可以将它们放在堆栈上。所以这个方法需要 O(N) 额外的堆内存。
  • 好点。在这方面,@XIII-th 下面的答案更好stackoverflow.com/a/58310635/326162
【解决方案5】:

您可以使用以下实现来比较两个Collection

infix fun <T> Collection<T>.deepEqualTo(other: Collection<T>): Boolean {
    // check collections aren't same
    if (this !== other) {
        // fast check of sizes
        if (this.size != other.size) return false
        val areNotEqual = this.asSequence()
            .zip(other.asSequence())
            // check this and other contains same elements at position
            .map { (fromThis, fromOther) -> fromThis == fromOther }
            // searching for first negative answer
            .contains(false)
        if (areNotEqual) return false
    }
    // collections are same or they are contains same elements with same order
    return true
}

或订购忽略变体:

infix fun <T> Collection<T>.deepEqualToIgnoreOrder(other: Collection<T>): Boolean {
    // check collections aren't same
    if (this !== other) {
        // fast check of sizes
        if (this.size != other.size) return false
        val areNotEqual = this.asSequence()
            // check other contains next element from this
            .map { it in other }
            // searching for first negative answer
            .contains(false)
        if (areNotEqual) return false
    }
    // collections are same or they are contains same elements
    return true
}

注意:这两个函数只比较第一级深度

【讨论】:

  • 第二个答案的复杂度为 O(N^2)。语句it in other 的列表复杂度为 O(N),被调用 N 次。第二种情况的正确解决方案类似于return this.toSet() == other.toSet()
  • @ManushinIgor 是的,您的解决方案比我的要好。谢谢
【解决方案6】:

:使用扩展功能的简短版本:

fun List<*>.deepEquals(other : List<*>) = 
    this.size == other.size && this.mapIndexed { index, element -> element == other[index] }.all { it }

你可以这样使用它:

listOf("Hola", "Mundo").deepEquals(listOf("Hello", "World"))

【讨论】:

  • 要提前中断并避免遍历整个列表,您应该使用this.asSequence().mapIndexed {...}
【解决方案7】:

你可以使用数组和contentDeepEquals:

infix fun <T> Array<out T>.contentDeepEquals(
    other: Array<out T>
): Boolean
JVM
1.1
@JvmName("contentDeepEqualsInline") infix fun <T> Array<out T>.contentDeepEquals(
    other: Array<out T>
): Boolean

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/content-deep-equals.html

【讨论】:

  • 请注意,此函数需要与true 答案相同的元素顺序。
【解决方案8】:

当我想与 kotlin 上的列表进行比较时,我喜欢这种方式

data class Element(val id: String, val name: String)
var list1 = mutableListOf<Element>()
var list2 = mutableListOf<Element>()
fun deleteRepeated(
        list1: List<Element>,
        newElementsList: List<Element>
    ): List<FileInfo> {
        return list2.filterNot { isTheSameID(it, list1) }
 }
 private fun isTheSameID(element: Element, list1: List<FileInfo>): Boolean {
     list1.forEach {
         if (element.id == it.id){
             return true
         }
     }
     return false
 }

list1 = [(id=1, name=Eva),(id=2, name=Ana), id=3, name=Abraham)]

list2 = [(id=2, name=Ana), id=3, name=Abraham)]

调用 deleteRepeated(list1, list2) 之后

list1 = [(id=1, name=Eva)]

【讨论】:

    【解决方案9】:

    您可以遍历一个列表并从第二个列表中检查相应的位置值。请参考以下示例。

    var list1 = mutableListOf<String>()
    var list2 = mutableListOf<String>()
    
    list1.forEachIndexed { i, value ->
        if (list2[i] == value)
        {
            // your implementaion
        }  
    }
    

    此外,您可以过滤列表中的更改值。

    var list1 = mutableListOf<String>()
    var list2 = mutableListOf<String>()
    
    val changedList = list1.filterIndexed { i, value -> 
        list2[i] != value)
    }
    

    【讨论】:

    • 如果列表大小不同,此方法可能会失败。此方法还在内存中分配新列表。
    【解决方案10】:

    到目前为止的问题和答案主要集中在equals/!equals,但由于标题谈到比较,我将给出一个稍微通用的答案,实现compareTo返回-1,0,1 for

    fun <T: Comparable<T>> Iterable<T>.compareTo(other: Iterable<T>): Int {
        val otherI = other.iterator()
        for (e in this) {
            if (!otherI.hasNext()) return 1 // other has run out of elements, so `this` is larger
            val c = e.compareTo(otherI.next())
            if (c != 0) return c // found a position with a difference
        }
        if (otherI.hasNext()) return -1 // `this` has run out of elements, but other has some more, so other is larger
        return 0 // they're the same
    }
    

    【讨论】:

      【解决方案11】:

      如果您想比较两个具有相同数量的相同元素且不关心顺序的列表的另一个答案:

      infix fun <T> List<T>.elementEquals(other: List<T>): Boolean {
        if (this.size != other.size) return false
      
        val tracker = BooleanArray(this.size)
        var counter = 0
      
        root@ for (value in this) {
          destination@ for ((i, o) in other.withIndex()) {
            if (tracker[i]) {
              continue@destination
            } else if (value?.equals(o) == true) {
              counter++
              tracker[i] = true
              continue@root
            }
          }
        }
      
        return counter == this.size
      }
      

      【讨论】:

        【解决方案12】:

        想说containsAll() 比仅排序和检查相等要慢。

        我已经使用在线 Kotlin 控制台对其进行了测试,结果如下:

        但最快的方法可能是改用Set。 (但是,set 不允许重复元素。所以要小心你的用例)

        【讨论】:

          猜你喜欢
          • 2011-06-19
          • 2013-03-03
          • 2018-03-19
          • 2016-08-24
          • 2019-04-18
          • 2016-04-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多