filterNotNull for Map values

  val map: Map<Int, String> = mapOf(1 to "1", 2 to "2")
  val map2: Map<Int, String?> = map.mapValues { if (it.key == 1) "1" else null }
  
  @Suppress("UNCHECKED_CAST")
  val map3: Map<Int, String> = map2.filterValues { it != null } as Map<Int, String>

Is there a good way to solve this without the cast, or can one be added?

I had some fun with this.

First, I think this cast is perfectly fine there.

This removes the cast, but at the cost of an additional intermediary data structure:

val map3: Map<Int, String> = map2.filterValues { it != null }.mapValues { it.value ?: "" }

Of course, you could go imperative. The closest “functionalish imperative” solution I came to is:

val map3: Map<Int, String>
    = hashMapOf<Int, String>().apply { map2.forEach { it.value?.apply { this.put(it.key, this) } } }

But this does not compile because I don’t know how to work with nested “receivers functions” (this in this.put should refer to the hashmap we just created, while the parameter this should refer to it.value).

Anyone knows how to deal with nested “receivers functions”? I tried this@HashMap to no avail.
Isn’t there something like this:

fun <T, R> with(e: T, f: (T) -> R): R

All such function in the standard libraries seem to involve a receiver.


Two other possible fixes that involve libraries changes:

  • add a filter method that can remove entries:

      fun <K1, V1, K2, V2> Map<K1, V1>.filterMap(f: (Map.Entry<K1, V1>) -> Map.Entry<K2, V2>?): Map<K2, V2>
    
  • using the Java8 stream API (not ported to Kotlin), you’d write something like

      val map3: Map<Int, String> = map2.stream.filterValues { it != null }.mapValues { it.value ?: "" }.toMap()
    

It’s very similar to the very first solution I proposed, but here you wouldn’t allocate an intermediary data structure.

I guess this was more a feature request than a question. In the meantime I solved this by hiding the cast behind my own extension function.

Looks like KT-4734 filterNotNullValues and filterNonNullKeys

1 Like

I figured out a sane way to do this without a cast:

val map3: Map<Int, String> = map2
    .mapNotNull { it.value?.let { value -> it.key to value} }
    .toMap()

I don’t think this solution is any better. Yes you get rid of the cast, but your code is much less readable.
I for one prefer the suppressed warning with the somewhat unnecessary cast over a solution which takes even an experienced programmer about a minute to understand. After all programming is about 90% reading code.