【问题标题】:How to merge list of maps to one map in Kotlin? [closed]如何将地图列表合并到 Kotlin 中的一张地图? [关闭]
【发布时间】:2021-07-09 07:48:21
【问题描述】:

我有一个 List> 我需要获取 Map

例如我有:

listOf(mapOf(Enum.First to setOf("one", "two", "three")),
 mapOf(Enum.Second to setOf("four", "five", "six")))

我需要:

mapOf(Enum.First to setOf("one", "two", "three"), Enum.Second to setOf("four", "five", "six"))

【问题讨论】:

  • 如果列表中有两个映射都具有键 Enum.First 怎么办?要合并集合吗?
  • 忘记了我在问这个问题时的近距离投票。我投票决定重新开放 - 主要是为了给出答案。

标签: java dictionary kotlin enums


【解决方案1】:

此函数将地图列表展平为单个地图。请注意,如果有任何重复键,则只会显示该键最后一次出现的值。

fun <K, V> flatten(list: List<Map<K, V>>): Map<K, V> =
    mutableMapOf<K, V>().apply {
        for (innerMap in list) putAll(innerMap)
    }

例子:

enum class Enum { First, Second }

fun main() {
    val list = listOf(
        mapOf(Enum.First to setOf("one", "two", "three")),
        mapOf(Enum.Second to setOf("four", "five", "six"))
    )
    val map = flatten(list)
    println(map)
}

输出:

{First=[one, two, three], Second=[four, five, six]}

【讨论】:

    【解决方案2】:

    首先,有一个特定优化的Map 实现来保存枚举键:EnumMap

    不幸的是,Kotlin 标准库中没有方便的函数用于创建 EnumMap(就像其他 Map 实现一样),所以你可以定义自己的(感谢 @Adam Millerchip 的想法):

    inline fun <reified K : kotlin.Enum<K>, V> enumMapOf() = EnumMap<K, V>(K::class.java)
    inline fun <reified K : kotlin.Enum<K>, V> enumMapOf(vararg pairs: Pair<K, V>) = pairs.toMap(EnumMap(K::class.java))
    

    其次,当原始列表中有两个地图具有相同的键(和不同的值)时,可能会有不同的策略来缓解这种情况:

    val list = listOf(
        mapOf(Enum.First to setOf("one", "two", "three")),
        mapOf(Enum.Second to setOf("four", "five", "six")),
        mapOf(Enum.First to setOf("four", "five", "six"))
    )
    

    简单的策略是“最新的胜利”:

    val res = list.fold(enumMapOf<Enum, Set<String>>()) { acc, map ->
        acc.apply { putAll(map) }
    }
    println(res) // {First=[four, five, six], Second=[four, five, six]}
    

    另一种方法是“合并值”:

    val resMerged = list.fold(enumMapOf<Enum, Set<String>>()) { acc, map ->
        map.forEach { (k, v) -> acc.merge(k, v) { old, new -> old.union(new) } }
        acc
    }
    println(resMerged) // {First=[one, two, three, four, five, six], Second=[four, five, six]}
    

    为方便起见,您可以定义一个用于合并地图的辅助扩展方法(并将union 与中缀调用一起使用):

    fun <K, V : Any> MutableMap<K, V>.merge(map: Map<K, V>, remappingFunction: (V, V) -> V) {
        map.forEach { (k, v) -> this@merge.merge(k, v, remappingFunction) }
    }
    
    val resMerged = list.fold(enumMapOf<Enum, Set<String>>()) { acc, map ->
        acc.apply { merge(map) { old, new -> old union new } }
    }
    println(resMerged) // {First=[one, two, three, four, five, six], Second=[four, five, six]}
    

    【讨论】:

    • ? +1 正要问 EnumMap 构造函数中的 Enum::class.java 是什么,但文档回答:“使用指定的键类型创建一个空的枚举映射。”
    • ? 直到EnumMap。将枚举映射构造函数包装起来可能会很好,以便可以将 Kotlin 样式的枚举映射实例化为emptyEnumMap&lt;Enum, Set&lt;String&gt;&gt;(),而不会出现嘈杂的::class.java:inline fun &lt;reified K : Enum&lt;K&gt;, V&gt; emptyEnumMap() = EnumMap&lt;K, V&gt;(K::class.java)。就个人而言,我发现将可变映射作为累加器传递给 list.fold() 令人惊讶,这就是我只使用 for 循环的原因:它清楚地表明映射正在发生变化。
    • @AdamMillerchip,感谢您的想法,更新了我的答案!我认为enumMapOf 函数名称会与stdlib 更一致,因为HashMapLinkedHashMap 创建有类似的函数(hashMapOflinkedMapOf),而其他empty* 方法(emptyArrayemptySet, emptySequence, emptyList) 返回不可变的数据结构。
    【解决方案3】:

    试试这个:

    fun getMapOf(listOfMaps: List<Map<Enum, Set<String>>>): Map<Enum, Set<String>> {
        return listOfMaps
            .flatMap { map -> map.entries }
            .associate(Map.Entry<Enum, Set<String>>::toPair)
    
    }
    

    【讨论】:

    • 在将它们放入新地图之前,这将为每个 Map.Entry 创建一个 Pair 毫无价值。也许associate { it.toPair() } 会更清楚?
    • 谢谢,现在更简洁了。
    • 更简洁,但我想指出这会创建 N+1 个额外的对象:一个额外的列表和 N 对。我的回答创建了 0 个额外的对象。
    • @AdamMillerchip 我认为my solution 也有相同的开销——为每个条目创建一对。不知道为什么 Kotlin 不允许/具有将相同类型的 MapEntries 列表转换为具有这些条目的 Map 的功能。
    • @aneroid 我也想知道缺少Map.Entry -> Map 函数。我想知道它是否被提议?这可能是他们会考虑添加的东西。 List&lt;Int&gt; -> IntArray 等有函数,所以 List&lt;Map.Entry&gt; -> Map 似乎是公平的游戏。
    【解决方案4】:

    我最初的方法是similar to Adam Millerchip's(mutableMap 和 putAll)。当列表中的每个地图包含多个条目时,效果会更好。

    但是,对于列表中每个地图只有一个条目的 OP 的特定情况,以下解决方案很方便:

    val map = list.map { it.entries.first().toPair() }.toMap()
    

    遍历列表,每个Map 使用entries.first() 来获取它的唯一Entry 并使其成为Pair。然后将得到的Pairs 列表转换为Map

    替代方案,使用Associate - 通过提供返回Pair(key, value) 的lambda 来使用associate(transform -&gt; Pair)

    val map = list.associate { it.entries.first().toPair() }
    

    并且由于 OP 没有指定如何处理具有相同键的 Map,“如果有任何重复的键,则只会出现该键最后一次出现的值。”

    第一个选项的示例函数:

    fun <K, V> singleEntryMapListToMap(list: List<Map<K, V>>) =
        list.map {
            it.entries.first().toPair()
        }.toMap()
    

    输出:

    {First=[one, two, three], Second=[four, five, six]}
    

    【讨论】:

    • it.entries 可能比it.asSequence() 更清晰?
    • @AdamMillerchip Doh!是的,这更干净,更清晰。更新。是的,我从历史中看到您在早期版本的答案中使用了它。
    猜你喜欢
    • 2017-12-25
    • 1970-01-01
    • 2019-05-02
    • 2020-10-19
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 2012-05-01
    • 1970-01-01
    相关资源
    最近更新 更多