Home > other >  Convert List<Pair<K, Collection<V>>> to Map<K,Collection<V>> in Kotlin
Convert List<Pair<K, Collection<V>>> to Map<K,Collection<V>> in Kotlin

Time:11-08

Does anyone know how to convert a List<Pair<K, Collection< V >>> to Map<K,Collection< V >> in Kotlin, merging the collections associated with the same key?

In Java I would use something like:

.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (c1, c2) -> {
      List result = new ArrayList<>();
      result.addAll(c1);
      result.addAll(c2);
      return result;
})

Does Kotlin have a similar device, or a better one? Any advice is appreciated.

CodePudding user response:

FYI, Kotlin has a toMap function exactly for that in cases where you don't need to merge the values of duplicate keys.

However, if you want to merge, you'll have to go with slightly more complicated. Assuming you have a list of pairs like this:

val pairs = listOf(
    1 to listOf("1", "2", "3"),
    1 to listOf("4"),
    2 to listOf("1", "2", "3"),
    3 to listOf("1", "2", "3"),
    3 to listOf("4", "5", "6"),
)

You can use groupBy and mapValues like this:

val map = pairs.groupBy({ it.first }, { it.second })
               .mapValues { (k, v) -> v.flatten() }

The groupBy will create a Map<Int, List<List<String>>>, so each key will be mapped to the list of all the values corresponding to it. Then the mapValues flattens each list of lists so it becomes a single list.

Note that this create an intermediate map and may not be very efficient in performance-sensitive code compared to the provided Java. If you need efficiency, you can use groupingBy instead to get a more lazy grouping and aggregate it as you please. For instance:

val map = pairs.groupingBy { it.first }
               .fold(emptyList<String>()) { acc, v -> acc   v.second }

which is nice but creates intermediate lists when merging. Or:

val map = pairs.groupingBy { it.first }.fold(
    initialValueSelector = { _, _ -> mutableListOf<String>() },
    operation = { _, acc, v -> acc.addAll(v.second); acc }
)

which creates a single list per key, but is a bit more complicated. Or another variant if you want to get crazier:

val map = pairs.groupingBy { it.first }.aggregate { k, acc: List<String>?, v, _ ->
    if (acc == null) v.second else acc   v.second
}

(which admittedly is a bit of a mouthful, and still creates intermediate lists)

CodePudding user response:

You can use associate to covert it. For exampe:

listOf(
    Pair(1, listOf("1", "2", "3")),
    Pair(2, listOf("1", "2", "3")),
    Pair(3, listOf("1", "2", "3"))
).associate { it.first to it.second }

The result is map:

{1=[1, 2, 3], 2=[1, 2, 3], 3=[1, 2, 3]}

If you need only first element as a value, you need to modify only value mapping to it.second.first().

  • Related