Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 587
arnab Avatar asked Nov 10 '16 17:11

arnab


3 Answers

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.

like image 70
Ilya Avatar answered Sep 29 '22 13:09

Ilya


.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
like image 26
kevinmost Avatar answered Sep 29 '22 12:09

kevinmost


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() ?: ""
like image 20
Kingsley Adio Avatar answered Sep 29 '22 13:09

Kingsley Adio