Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collect to map skipping null values

Tags:

kotlin

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?>

like image 236
Eldar Agalarov Avatar asked Mar 18 '18 10:03

Eldar Agalarov


People also ask

Can collectors toMap return null?

toMap throws a NullPointerException if one of the values is null .

Does map accept null values?

It doesn't allow nulls. So consider using a TreeMap when you want a Map sorts its key-value pairs by the natural order of the keys. Points to remember: Map doesn't allow duplicate keys, but it allows duplicate values.

How do you handle a null key on a map?

If you pass null as map key, it will go to 0 bucket . All values of null key will go there. That is why it returns same value, cause all keys you are providing are null and are in the same bucket of your HashMap.


3 Answers

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.

like image 168
Salem Avatar answered Nov 14 '22 21:11

Salem


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)
like image 45
pwolaq Avatar answered Nov 14 '22 21:11

pwolaq


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.

like image 30
LostMekkaSoft Avatar answered Nov 14 '22 20:11

LostMekkaSoft