Reputation: 23129
Given a map, and a list of keys
val abc = mapOf(1 to "a", 2 to "b", 3 to "c")
val keys = listOf(1, 2)
How do I a get a map containing only the key-value pairs specified by keys
? Something like
val ab = abc.slice(keys)
// equivalent to mapOf(1 to "a", 2 to "b)
I'm looking for something a bit more elegant than
val ab = listOf(1, 2).map { it to abc[it] }.toMap()
For example, in Elixir:
abc = %{1 => "a", 2 => "b", 3 => "c"}
ab = Map.take(abc, [1, 2])
# equivalent to ab = %{1 => "a", 2 => "b"}
Upvotes: 5
Views: 3313
Reputation: 26200
A minor variation of @spyro's efficient answer:
fun <K, V> Map<K, V>.slice(keys: Collection<K>): Map<K,V> =
buildMap {
keys.forEach { key ->
this@slice[key]?.let { value ->
put(key, value)
}
}
}
Upvotes: 0
Reputation: 515
fun <K, V> Map<K, V>.slice(keys: Collection<K>): Map<K,V> {
val resultMap = mutableMapOf<K, V>()
for (key in keys) {
get(key)?.let{resultMap[key] = it}
}
return resultMap
}
Test:
@Test
fun `should slice map by keys`() {
// GIVEN
val inputMap = (1..10_000).associate {
UUID.randomUUID() to "value $it"
}
val nonExistingKey = UUID.randomUUID()
val filterKeys = inputMap.keys.take(999).shuffled() + nonExistingKey
// WHEN
val startSlice = System.currentTimeMillis()
val filteredMapSlice = inputMap.slice(filterKeys)
println("Duration of slice: ${System.currentTimeMillis() - startSlice} ms")
val startFilterKeys = System.currentTimeMillis()
val filteredMapFilterKeys = inputMap.filterKeys { it in filterKeys }
println("Duration of filterKeys: ${System.currentTimeMillis() - startFilterKeys} ms")
assertThat(filteredMapFilterKeys).isEqualTo(filteredMapSlice)
// THEN
assertThat(filteredMapSlice).hasSize(filterKeys.size - 1) // non-existing key should have been filtered
assertThat(filteredMapSlice.keys).doesNotContain(nonExistingKey)
assertThat(filteredMapSlice.keys).allMatch { it in inputMap.keys }
filteredMapSlice.forEach{ (key, value) ->
assertThat(value).isEqualTo(inputMap[key])
}
}
This is magnitudes (!) faster than .filterKeys()
Duration of slice: 3 ms
Duration of filterKeys: 1479 ms
Upvotes: 1
Reputation: 8355
Solutions given in above answers does solve the problem but I think a small change is warranted.
Problem is that for every key
in the map
they check if the list
contains
that key
, which is O(n)
operation, this is ok for small lists but once you reach a certain size it becomes very slow. I suggest that you convert the list
of keys to a set
which reduces the contains operation to O(1)
in average case. (Hence reducing carbon footprint :) ).
Following is the solution with above change incorporated.
val mapAbc = mapOf(1 to "a", 2 to "b", 3 to "c")
val keySet = listOf(1, 2).toSet()
val filteredMap = mapAbc.filterKeys { it in keySet }
Upvotes: 3
Reputation: 14415
You can use filterKeys
:
val ab = abc.filterKeys { it in keys }
And since it is Kotlin, you could even define your own extension function to achieve exactly what you imagined:
fun <T> Map<T, *>.slice(keys: Iterable<T>) = filterKeys { it in keys }
val ab = abc.slice(keys)
Upvotes: 5