Eldar
Eldar

Reputation: 5227

Collect to map skipping null values

How to collect to Map from List where null values are excluded/skipped?

This code doesn't skip null values:

val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
    .associateBy({ it.first }, { it.second })
println(map)

Workaround solution. But collects into mutable map:

val map2 = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
    .mapNotNull {
        if (it.second != null) it else null
    }.toMap()    
println(map2)

So is there more convenient way to do this? Also I want to get Map<String, Int> type, not Map<String, Int?>

Upvotes: 37

Views: 35764

Answers (4)

LostMekkaSoft
LostMekkaSoft

Reputation: 363

Since Kotlin 1.6, there is also a stable buildMap function that can be used to write custom helper functions that are performant without sacrificing readability:

fun <T, K : Any, V : Any> Iterable<T>.associateByNotNull(
    keySelector: (T) -> K?,
    valueTransform: (T) -> V?,
): Map<K, V> = buildMap {
    for (item in this@associateByNotNull) {
        val key = keySelector(item) ?: continue
        val value = valueTransform(item) ?: continue
        this[key] = value
    }
}

Note that writing this as a "low-level" for loop eliminates the need for the creation of intermediate collections.

Upvotes: 14

Salem
Salem

Reputation: 14907

Actually, a slight change to pwolaq's answer guarantees that the second item is non-nullable:

val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
    .mapNotNull { p -> p.second?.let { Pair(p.first, it) } }
    .toMap()
println(map)

This will give you a Map<String, Int>, since mapNotNull ignores anything that maps to null, and using let with the safe call operator ?. returns null if its receiver (p.second) is null.

This is basically what you stated in your question, made shorter with let.

Upvotes: 45

Steven Kok
Steven Kok

Reputation: 49

A more readable solution might be using the associateBy function with the double bang expression (!!):

val map: Map<String, Int> = listOf(
        Pair("a", 1),
        Pair("b", null),
        Pair("c", 3),
        Pair("d", null)
)
.filter { it.first != null && it.second != null }
.associateBy({ it.first!! }, { it.second!! })

println(map)

Upvotes: 3

pwolaq
pwolaq

Reputation: 6381

You want to filter out null values, then you should use filter method:

val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
    .filter { it.second != null }
    .toMap()
println(map)

Upvotes: 9

Related Questions