Search code examples
kotlintransformationidioms

Is there a function in the Kotlin stlib for transforming a List<Pair<A, B>> into a Pair<List<A>, List<B>>


I currently have code with similar mechanics to the following

fun main() {
    val listOfLists = listOf(listOf("1234", "1", "42"), listOf("hello", "there"))
    
    val (lengths: List<List<Int>>, resultingListsOfLists: List<List<String>?>) =
        listOfLists
            .map {
                val lengths = it.map { it.count() }
                Pair(
                    lengths,
                    if (lengths.sum() > 5) {
                        // doing some transformation here in my original code,
                        // but for the sake of keeping it simple, just return `it`
                        it 
                    } else {
                        null
                    }
                )
            }
            .let { mapped ->
                println(mapped)
                Pair(mapped.map { it.first }, mapped.map { it.second })
            }
            
    println(lengths)
    println(resultingListsOfLists)
}

which should output

[([4, 1, 2], [1234, 1, 42]), ([5, 5], [hello, there])]
[[4, 1, 2], [5, 5]]
[[1234, 1, 42], [hello, there]]

and it works sufficiently for my use case.

However, the last let-part is a bit verbose. Knowing Kotlin, I feel there should be a way to make transforming List<Pair<A, B>> to a Pair<List<A>, List<B>> more concise and readable.

Is there a function in the stdlib to achieve this?

I know about associate, but this wouldn't allow destructuring the resulting Pair and there could be issues with duplicate keys.


Solution

  • You are looking for unzip - a method that converts List<Pair<T, U>> to Pair<List<T>, List<U>>. You can replace the final let call with unzip.

    If you extract the "transformation" you do into another function, you can write something very readable, like it.takeIf { ... }?.run(::process).

    listOfLists
        .map {
            val lengths = it.map(String::length)
            lengths to (it.takeIf { lengths.sum() > 5 }?.run(::process))
        }.unzip()