我有两个列表:一个包含旧数据,其中应保留 Boolean
,以及应与旧数据合并的新数据 . 这个单元测试最好看出这个:
@Test
fun mergeNewDataWithOld() {
// dog names can be treated as unique IDs here
data class Dog(val id: String, val owner: String)
val dogsAreCute: List<Pair<Dog, Boolean>> = listOf(
Dog("Kessi", "Marc") to true,
Dog("Rocky", "Martin") to false,
Dog("Molly", "Martin") to true
)
// loaded by the backend, so can contain new data
val newDogs: List<Dog> = listOf(
Dog("Kessi", "Marc"),
Dog("Rocky", "Marc"),
Dog("Buddy", "Martin")
)
// this should be the result: an intersection that preserves the extra Boolean,
// but replaces dogs by their new updated data
val expected = listOf(
newDogs[0] to true,
newDogs[1] to false
)
// HERE: this is the code I use to get the expected union that should contain
// the `Boolean` value of the old list, but all new `Dog` instances by the new list:
val oldDogsMap = dogsAreCute.associate { it.first.id to it }
val newDogsMap = newDogs.associateBy { it.id }
val actual = oldDogsMap
.filterKeys { newDogsMap.containsKey(it) }
.map { newDogsMap[it.key]!! to it.value.second }
assertEquals(expected, actual)
}
我的问题是:有什么更好的方法来编写代码来获取我的 actual
变量?我特别不喜欢我首先过滤两个列表中包含的键,但是我必须明确地使用 newDogsMap[it.key]!!
来获取空值安全值 .
我怎样才能改进它?
编辑:问题已重新定义
感谢Marko:我想做一个十字路口,而不是一个工会 . 在列表上做一个交集是很容易的:
val list1 = listOf(1, 2, 3)
val list2 = listOf(4, 3, 2)
list1.intersect(list2)
// [2, 3]
但我真正想要的是 Map 上的交叉点:
val map1 = mapOf(1 to true, 2 to false, 3 to true)
val map2 = mapOf(4 to "four", 3 to "three", 2 to "two")
// TODO: how to do get the intersection of maps?
// For example something like:
// [2 to Pair(false, "two"), 3 to Pair(true, "three")]
3 回答
干得好:
请注意,我只关注“你如何在这里省略
!!
”;-)或者其他方式当然也是作品:
您只需要有适当的外部条目,并且您(
null
- )是安全的 .这样你就可以省去所有
null
-safe操作(例如!!
,?.
,mapNotNull
,firstOrNull()
等) .另一种方法是将
cute
作为属性添加到data class Dog
,并使用MutableMap
代替新狗 . 这样,您可以使用自己的合并函数适当地merge
值 . 但正如你在评论中所说的那样,你不需要MutableMap
,所以那时候就不行了 .如果您不喜欢这里发生的事情而宁愿将其隐藏给任何人,您也可以提供适当的扩展功能 . 但命名它可能已经不是那么容易......这是一个例子:
现在,您可以在任何想要通过键交叉 Map 的位置调用此函数,并立即映射到其他值,如下所示:
请注意,我还不是命名的忠实粉丝 . 但是你会明白这一点;-)函数的所有调用者都有一个很好/很短的接口,不需要理解它是如何真正实现的 . 然而,该功能的维护者当然应该对其进行测试 .
也许还有类似下面的帮助?现在我们介绍一个中间对象只是为了让命名更好...仍然没有说服,但也许它可以帮助某人:
如果你走这条路,你应该注意中间对象应该被允许做什么,例如现在我可以从那个中间体中取出
map1
或map2
,如果我查看它的名字可能不合适......所以我们有下一个施工现场;-)为简化起见,假设您有以下内容:
内存和性能方面的最佳选择是直接在可变映射中更新条目:
如果您有理由坚持使用不可变映射,则必须分配临时对象并进行更多工作 . 你可以这样做:
你可以尝试类似的东西:
这首先将可爱的狗与一只新狗配对或无效,然后如果有一只新狗,则映射到该对:来自原始 Map 的新狗和可爱信息 .
更新:Roland是对的,这将返回
List<Pair<Dog?, Boolean>>
的类型,因此这里是针对此方法的类型的建议修复:很可能他在使用
flatMap
的另一个答案中的方法是一个更复杂的解决方案 .