Kotlin 将 List<Pair<K, Collection<V>>> 转换为 Multimap

dco*_*led 4 key-value multimap kotlin

我正在寻找一种转换成对列表的惯用方法,其中Pair.first是键,Pair.second是值列表。这种过程方法有效,但我希望找到一种更惯用的方法,不需要直接创建可变列表。

val pairs: Pair<String, List<Int>>

val res = mutableMapOf<String, List<Int>>()
pairs.forEach {
    res.getOrPut(it.first, ::mutableListOf).addAll(it.second)
}
Run Code Online (Sandbox Code Playgroud)

这段代码可以封装在扩展函数中,如下所示,但它看起来不太通用:

fun <K, V> List<Pair<K, Collection<V>>>.toMultimap(): Map<K, List<V>> {
    var res = mutableMapOf<K, MutableList<V>>()
    forEach {
        res.getOrPut(it.first, ::mutableListOf).addAll(it.second)
    }
    return res
}
Run Code Online (Sandbox Code Playgroud)

使用pairs.toMap不起作用,因为它会使用“最后获胜”方法覆盖地图键。 groupBy工作很接近,它在列表结构的列表中创建值的键。

val pairs2 = listOf(
    Pair("a", listOf(1, 2, 3)),
    Pair("b", listOf(6, 7)),
    Pair("a", listOf(4, 5)),
    Pair("b", listOf(8, 9)),
)

val res = pairs2.groupBy({ it.first }, { it.second })
println(res)
Run Code Online (Sandbox Code Playgroud)

{a=[[1, 2, 3], [4, 5]], b=[[6, 7], [8, 9]]}

然后可以展平映射,但缺点是效率相当低,因为这会为每个键创建双倍所需的哈希映射和列表(一个用于 groupby,另一个用于展平)。如果有

val res = pairs2.groupBy({ it.first }, { it.second }).mapValues { it.value.flatten() }
println(res)
Run Code Online (Sandbox Code Playgroud)

{a=[1,2,3,4,5],b=[6,7,8,9]}

看看是否有更好的方法来完成这种转变。

Swe*_*per 5

而不是groupBy使用groupingBy,它会产生一个Grouping. 这是一个中间对象,您可以在其上执行各种折叠/缩小操作。在你的情况下:

fun <K, V> List<Pair<K, Collection<V>>>.toMultimap() =
    groupingBy { it.first }
        .fold(emptyList<V>()) { acc, (_, new) -> acc + new }
Run Code Online (Sandbox Code Playgroud)

+如果您不喜欢创建太多新列表的事实,您可以执行以下操作:

groupingBy { it.first }
    .fold({ _, _ -> mutableListOf<V>() }) { _, acc, (_, new) ->
        acc.addAll(new)
        acc
    }
Run Code Online (Sandbox Code Playgroud)