从Kotlin的另一个列表中的对象更新列表中的对象

时间:2019-05-17 14:14:51

标签: kotlin

是否有更好的方式编写以下内容:

private fun updateImportantProperty(
    firstList: List<MyObjectX>?,
    secondList: List<MyObjectX>?
) {
    firstList?.forEach { item1 ->
        secondList?.forEach { item2 ->
            if (relatesInSomeWayToEachOther(item1, item2)) {
                item1.importantProperty = item2.importantProperty
            }
        }
    }
}

以上代码的结果可能是firstList的1个对象已更新或7个对象已更新(如果列表具有7个公共对象)。

我只想更新firstList中对象的一个​​重要属性。假设列表中没有任何内容,例如排序或大小,或者所有对象都在两个列表中。可读性是我的追求。

3 个答案:

答案 0 :(得分:1)

如果列表变大,则遍历第二个列表中第一个列表中每个项目的速度就会变慢,这需要花费O(n^2)时间。

如果匹配项很少见(即,每个列表的第一个项目不会有很多匹配项,那么只有少数几个),您可能希望通过提取真正有意义的内容来建立某种索引从第二个列表的所有项目中选择relatesInSomeWayToEachOther,然后对第一个列表的每个项目进行快速查找。

例如:

val secondListIndex = secondList.groupBy { getIndexKey(it) }

firstList.forEach { item1 ->
    val matchingSecondListItems = secondListLookup[getLookupKey(item1)].orEmpty()
    matchingSecondListItems.forEach { item2 ->
        item1.importantProperty = item2.importantProperty
    }
}

我在这里使用了两个函数getIndexKeygetLookupKey,但是如果匹配条件很简单,则它可能是相同的函数。

答案 1 :(得分:1)

因此,您基本上想通过将某些属性值替换为第二个列表中的最后一个匹配属性来映射第一个列表。如果您要提供更多的上下文,肯定会更容易,但是我将尝试简化您的语法:

firstList?.map { 
  firstListElement -> secondList
    ?.filter { it.relatesInSomeWayTo(firstListElement) }
    ?.lastOrNull()
    ?.let { firstListElement.copy(property = it.property) }
      ?: firstListElement 
}

我相信这将是科特林的一种方式。

也请注意两个可选的调整-首先,您的“ relatesTo”函数应该是MyObjectX上的扩展函数。其次,它应该是不可变的,因此,请使用复制而不是分配属性。

编辑: 抱歉,无法编译。 filter函数返回列表,而不是对象,因此您需要添加lastOrNull()调用。固定;)

答案 2 :(得分:0)

假设Map没有意义,并且正如您提到的时间复杂度不是问题,我将仅关注如何在可读性方面改进该代码。

尽管如此,我还是会介绍一些性能方面的改进,即,如果第二个列表为空/ firstList.forEach,则不必调用null。另外,由于这似乎是一个非常特殊的用例,我宁愿为其添加一个扩展功能,以便更加清楚地知道哪个列表是由什么来更新的,例如:

fun List<MyObjectX>.updateBy(other: List<MyObjectX>?) {
  if (other != null && other.isNotEmpty())
    forEach { thisItem ->
      other.filter { relatesInSomeWayToEachOther(thisItem, it) }
           .forEach { thisItem.importantProperty = it.importantProperty }
    }
}

因此,我认为使用它更具可读性,例如:

val oneNullable : List<MyObjectX>? = ...
val twoNotNull : List<MyObjectX>? = ...

oneNullable?.updateBy(twoNotNull)
// or vice-versa
twoNotNull.updateBy(oneNullable)

关于,一旦内循环稍有变化,最多应更新的对象:

fun List<MyObjectX>.updateOnceBy(other: List<MyObjectX>?) {
  if (other != null && other.isNotEmpty()))
    forEach { thisItem ->
      other.firstOrNull { 
        relatesInSomeWayToEachOther(thisItem, it)
      }?.also {
        thisItem.importantProperty = it.importantProperty
      }
    }
}

好吧...我明白了...我更看重调用方的可读性,而不是实现方的可读性;-)

也许将其拆分有助于提高可读性:

fun MyObjectX.updateBy(other: List<MyObjectX>) {
  other.filter { this == it } // or if only first applies, firstOrNull again
       .forEach { importantProperty = it.importantProperty }
}

fun List<MyObjectX>.updateBy(other: List<MyObjectX>?) {
  if (other != null && other.isNotEmpty())
    forEach {
      it.updateBy(other)
    }
}

但是我认为类似Map这样的东西不仅会提高时间复杂度,而且会提高可读性...