Idiomatic way to flatten a list of pairs into a list in Kotlin

Viewed 488

The title should be pretty much explanatory. I have List<Pair<T, T>> and I would like to transform it into List<T> with all elements from all of the pairs in an idiomatic and (more importantly) efficient way.

I know there's flatten but it's for the case of List<List<T>> -> List<T>.

For now I came up with two implementations as extension functions:

fun <T> List<Pair<T, T>>.flatten(): List<T> {
    val accumulator = mutableListOf<T>()
    this.forEach {
        accumulator.add(it.first)
        accumulator.add(it.second)
    }
    return accumulator
}

and

fun <T> List<Pair<T, T>>.flatten2(): List<T> {
    return this.fold(mutableListOf()) { accumulator: MutableList<T>, pair: Pair<T, T> ->
        accumulator.add(pair.first)
        accumulator.add(pair.second)
        accumulator
    }
}

but they don't seem to be elegant.

Could it be done better (in terms of readability, efficiency and idiomaticness)?

2 Answers

I think both your solutions are very readable so you don't need much improvement there.

In terms of efficiency, a loop in this case would always be the best, although you should preallocate the list's size to further optimise it:

val accumulator = ArrayList<T>(size * 2)

In terms of it being idiomatic, I really think that for a simple extension function it doesn't need to be any better than this. The obvious way is to use a flatMap as the other answer mentions, however in my opinion this makes it a lot less efficient when it doesn't need to be. Out of the two, the second is more idiomatic and the performance should be identical, so go with your preference.

The most elegant I can think of is to use flatMap, which is a more general form of flatten - doing a map before flattening.

fun <T> List<Pair<T, T>>.flattenPairs(): List<T> =
        this.flatMap { (x, y) -> listOf(x, y) }

This is probably less efficient than your solution though, as it creates a lot of intermediate lists. If you want to avoid that, your last attempt can be cleaned up a little bit like this:

fun <T> List<Pair<T, T>>.flatten2(): List<T> =
    this.fold(mutableListOf()) { accumulator, (x, y) ->
        accumulator.apply {
            add(x)
            add(y)
        }
    }
  • The type annotations on the lambda parameters aren't required.
  • The pair can be deconstructed.
  • Use the apply scope function to reduce the duplicated word accumulator.
  • Changed to an expression body
Related