Skip to Content
Course content

84: Map Operations: mapValues, mapKeys, filterValues

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, I've seen this a dozen times: you're working with a Map, you want to transform the values, and your instinct is to reach for the .map { ... } function because that's what you've used for Lists your entire life. It feels like the logical choice. But in Kotlin, doing this with a Map leads to a very specific "Wait, what happened?" moment.

Thinking .map() returns another Map

Let's say you have a map of users and their current account balances, and you want to apply a 10% bonus to everyone. You might try something like this:

val balances = mapOf("Alice" to 100.0, "Bob" to 50.0)
val boostedBalances = balances.map { (name, balance) -> 
    name to balance * 1.1 
}
// You're expecting a Map, but boostedBalances is actually a List<Pair<String, Double>>!

Here is the catch: .map() is an extension function on Iterable. Since a Map implements Iterable<Map.Entry<K, V>>, it works, but it treats your map like a collection of entries. It transforms each entry into something else and collects those results into a List. Unless you manually call .toMap() at the end, you've just destroyed your Map structure. It's a tedious extra step that feels clunky.

Preserving the Map structure with mapValues and mapKeys

If you want to change the values but keep the keys exactly as they are, use mapValues. It's cleaner, more intentional, and returns a Map immediately. I always prefer this because it tells anyone reading your code exactly what's changing and what's staying put.

val balances = mapOf("Alice" to 100.0, "Bob" to 50.0)
val boostedBalances = balances.mapValues { entry -> 
    entry.value * 1.1 
}
// Now boostedBalances is actually a Map<String, Double>

Similarly, if you need to transform the keys—say, you have user IDs and you want to prefix them with a system code—you use mapKeys. You rarely use both in the same chain, but they are incredibly powerful for data normalization. For example, if you're receiving raw API keys that need to be uppercase before being used as lookups, mapKeys { it.key.uppercase() } is your best friend.

Pruning the noise with filterValues

Once you've transformed your data, you often find you have "junk" in there—values that don't meet your criteria. You could use the generic .filter { ... }, but just like with .map(), that returns a List<Map.Entry>. To keep your Map intact while removing entries, use filterValues.

Let's tie it all together. Imagine we're processing a simple game leaderboard:

val playerScores = mapOf("PlayerOne" to 150, "PlayerTwo" to 80, "PlayerThree" to 200)

val elitePlayers = playerScores
    .mapValues { it.value * 2 } // Double the scores for a special event
    .filterValues { it > 200 }  // Only keep those who now have over 200 points

println(elitePlayers) // {PlayerOne=300, PlayerThree=400}

Notice how the flow stays within the "Map world." We didn't have to convert back and forth between Lists and Maps, which keeps the code readable and prevents the performance overhead of creating unnecessary intermediate lists.




📋 Practical Task

Refining the RPG Inventory Map

You are building an inventory system for an RPG. You have a map where the keys are internal item IDs (Strings) and the values are the current quantities of those items (Integers). Your task is to process this map for the UI display.

Requirements:

  • Use mapKeys to transform every single item ID so that it is prefixed with "ITEM_".
  • Use mapValues to convert the quantity into a descriptive string. If the quantity is 1, it should be "1 unit"; otherwise, it should be "X units".
  • Use filterValues to remove any items that have "0 units" (effectively removing out-of-stock items from the display).
fun processInventory(inventory: Map<String, Int>): Map<String, String> {
    // Your code here
}

// Test case:
val rawInventory = mapOf("sword_01" to 1, "potion_health" to 5, "shield_iron" to 0, "gold_coin" to 100)
val result = processInventory(rawInventory)
// Expected result: {ITEM_sword_01=1 unit, ITEM_potion_health=5 units, ITEM_gold_coin=100 units}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.