or9ob
or9ob

Reputation: 2392

How to use `filter` on Map instance in Kotlin?

I see that #filter is defined on Map, but I am not able to figure out how to use it. Anyone care to share an example?

I have a deeply nested TreeMap instance (TreeMap<String, Map<String, Map<*, *>>>) and I want to filter/find the first (which is the only in the domain) top-level key that has a certain characteristics associated with something deeper in the value.

Here's how the data looks like:

{
  "i1": {
    "aliases": {}
  },
  "i2": {
    "aliases": {}
  },
  "i3": {
    "aliases": {}
  },
  "i4": {
    "aliases": {
      "alias-im-looking-for": {}
    }
  }
}

I have this following non-functional code which solves it right now:

val indexToAliasMappingType = LinkedTreeMap<String, Map<String, Map<*, *>>>()
val indexToAliasMappings = Gson().fromJson(response.jsonString, indexToAliasMappingType.javaClass)

var currentIndexName = ""
for ((index, aliasMappings) in indexToAliasMappings) {
    val hasCurrentAlias = aliasMappings.get("aliases")?.containsKey(alias)
    if (hasCurrentAlias != null && hasCurrentAlias) {
        currentIndexName = index
    }
}

return currentIndexName

Upvotes: 11

Views: 12880

Answers (3)

Ilya
Ilya

Reputation: 23115

You cannot invoke firstOrNull on the map itself, but you can invoke it on its entries set:

 val currentIndexName = indexToAliasMappings.entries.firstOrNull {
     it.value["aliases"]?.containsKey(alias) == true
 }?.key

firstOrNull iterates through map entries and stops on a first entry matching the predicate. No intermediate map allocations as in the variant with filter or filterValues are required here.

Upvotes: 5

Kingsley Adio
Kingsley Adio

Reputation: 728

Looks like what you need is the key part of the first entry that matches your criteria. Something like this (single line intentional) should help:

val currentIndexName = indexToAliasMappings.filterValues { it["aliases"]?.containsKey(alias) ?: false }.keys.firstOrNull() ?: ""

Upvotes: 6

kevinmost
kevinmost

Reputation: 595

.filter's lambda returns a boolean for each entry in the map. If that boolean evaluates to true, that entry is part of the returned collection. If that boolean evaluates to false, that entry is filtered out.

val indexToAliasMapping = linkedMapOf(
    "i1" to mapOf(
        "aliases" to mapOf<Any, Any>()
    ),
    "i2" to mapOf(
        "aliases" to mapOf<Any, Any>()
    ),
    "i3" to mapOf(
        "aliases" to mapOf<Any, Any>()
    ),
    "i4" to mapOf(
        "aliases" to mapOf(
            "aliases-im-looking-for" to "my-alias"
        )
    )
)
// If you only need one, you could also use .first instead of .filter, which
// will give you a Pair<String, Map<String, Map<*, *>>> instead of a List
val allWithNonEmptyAliases: Map<String, Map<String, Map<*, *>>> = indexToAliasMapping.filter {
    it.value["aliases"]?.containsKey(alias) ?: false
}

// We use .toList() so you can get the first element (Map doesn't allow you to retrieve by index)
return allWithNonEmptyAliases.toList().first().first

Upvotes: 2

Related Questions