JavaRush/Courses/Kotlin SELF/Map and MutableMap: reading V?, updating by key, the “cou...

Map and MutableMap: reading V?, updating by key, the “counter” pattern

Open

1. Why you need a Map and how to read Map<K, V>

When you’re just getting used to collections, it’s very easy to think like this: “I need a list, so List.” And that’s true… until you run into a task where the key to the data is not a position (0, 1, 2…), but some meaningful label: a product name, a team, a login, a category, an ID.

Map<K, V> (a map, a dictionary) stores pairs “key → value”. Keys are unique: two identical keys cannot exist in the same map. But values can repeat as much as you want: for example, products may have the same price, but the keys (product names) are still different.

Key point: Map in Kotlin is a separate “world”; it does not inherit from Collection, even though it’s still considered part of collections as a standard library data type.

How to read the type Map<K, V> without being scared of angle brackets

When you see Map<String, Int>, your brain may pretend it’s busy: “let’s do it later, I’m not ready.” That’s normal. The important thing is to learn to read the type out loud like a sentence. Map<K, V> is read as “a map from keys of type K to values of type V.” So Map<String, Int> is “keys are strings, values are integers.”

In real life this could be, for example, “product name → price”, “category → total spending”, “word → number of times it occurred”. No magic: the angle brackets just specify what exactly we put in and take out.

Let’s look at a “pocket price list”:

fun main() {
    val prices: Map<String, Int> = mapOf(
        "tea" to 90,
        "coffee" to 150
    )

    println(prices) // {tea=90, coffee=150}
}

Notice to. This is a convenient way to create a “key-value” pair so that mapOf(...) reads like “a list of mappings”.

2. Reading from Map: why you get V? and how to live with it

If you’re used to List, you expect the logic “give me an element by index — and you get an element”. But in a Map, a key may be absent. And Kotlin does a very reasonable thing: instead of “blowing up” with an error, a lookup by key returns null if the key is not found.

That’s exactly why the expression map[key] has the type V?, even if V itself is not nullable. This is one of the most important details, and it will come up constantly while you write any programs with Map.

fun main() {
    val prices = mapOf("tea" to 90, "coffee" to 150)

    val a: Int? = prices["tea"]
    val b: Int? = prices["milk"]

    println(a) // 90
    println(b) // null
}

There’s no “error” here. milk is simply missing — and that’s a normal situation, not a catastrophe.

Elvis ?: when reading: “if the key is missing, take a default value”

Once you accept the reality of V?, the next question is: “OK, what do I do if null doesn’t work for me?” In simple scenarios, the best tool is the Elvis operator ?:: “if the left side is null, then take the right side”.

This is especially convenient for numbers, when you have a “natural zero”. For example, if there’s no price — treat the price as 0 (or “unknown”, but that’s a different design).

fun main() {
    val prices = mapOf("tea" to 90, "coffee" to 150)

    val milkPrice = prices["milk"] ?: 0
    val teaPrice = prices["tea"] ?: 0

    println(milkPrice) // 0
    println(teaPrice)  // 90
}

Here we explicitly choose a policy: “no key → 0”. The important part is that Kotlin forces you to make a decision, instead of pretending the key “definitely exists”.

Checking whether a key exists: key in map as a readable style

Sometimes a default value doesn’t fit. For example, if a team doesn’t exist, you want to print an error message rather than “quietly” continue. Then we first check whether the key exists in the map.

Kotlin lets you write this very readably: if (key in map). Semantically, it’s exactly “if the key is in the map’s key set”.

fun main() {
    val prices = mapOf("tea" to 90, "coffee" to 150)
    val item = "tea"

    if (item in prices) {
        println("Price: ${prices[item]}") // Price: 90
    } else {
        println("No such item")           // (won't execute)
    }
}

Here prices[item] still returns Int?, but in this form of code you’re usually already confident that the key exists. A bit later in the course you’ll learn to do this even more neatly, but at the current stage this is enough: a check + a clear behavior branch.

3. MutableMap: writing, updating, and deleting

Up to this point we used mapOf(...), i.e., a read-only Map. Its job is to “store and allow reads”. But if you need to change data while the program is running, use MutableMap.

In Kotlin this split is the same as with List: a read-only type (Map) and a mutable type (MutableMap). At the same time, MutableMap supports write operations: you can add new pairs and update existing values.

One form for both “add” and “update”

The nicest thing is that writing looks the same for both “add” and “update”:

fun main() {
    val stock = mutableMapOf<String, Int>()

    stock["apple"] = 3
    stock["apple"] = 4
    stock["banana"] = 1

    println(stock) // {apple=4, banana=1}
}

So the [] operator for MutableMap works in two directions: read map[key] and write map[key] = value. This is one reason why maps in Kotlin feel like an “honest dictionary” rather than something bulky.

A small practical detail: the default standard implementation of MutableMap is LinkedHashMap, and when iterating it preserves insertion order. Today we won’t iterate over a map (that’s the topic for the next day), but it’s useful to understand why when you do println(map) you often see “pairs in the expected order”.

Deleting by key: remove(key) and “what exactly was returned”

Deletion in MutableMap is usually done like this: remove(key). And here Kotlin again behaves “like an adult”: the method returns the value that was removed, or null if the key wasn’t there.

This is handy because with a single expression you can find out whether there was anything to remove.

fun main() {
    val stock = mutableMapOf("apple" to 4, "banana" to 1)

    val removed: Int? = stock.remove("banana")
    val removedAgain: Int? = stock.remove("banana")

    println(removed)      // 1
    println(removedAgain) // null
    println(stock)        // {apple=4}
}

Same philosophy again: “missing key” is a normal state of the world, not an exception.

Mini table: what the basic Map operations return

Sometimes it’s easier to see the “contract” of operations once than to read an explanation ten times. In this section we’ll carefully fix what types are returned so you stop being surprised by ? in unexpected places (that surprise is one of the most common sources of irritation for beginners).

Operation Example What is returned Why
Read by key
prices["tea"]
V?
the key may be missing
Key check
"tea" in prices
Boolean
just the fact of presence
Write/update
stock["tea"] = 100
Unit
it’s an action, not a “value”
Delete
stock.remove("tea")
V?
you can find out what was deleted

This table is not “something to memorize”, but “something to get used to”. After a couple of exercises your brain will start automatically expecting a nullable when reading from a map.

4. The “counter” pattern: incrementing the value by key

Now comes one of the most practical techniques. It appears everywhere: statistics, counting, frequencies, “how many times a user did X”, “how much spending is in category Y”. And you can do it literally in one line.

The problem sounds like this: “I want to increment a counter by key. But if the key doesn’t exist, the counter should be treated as 0”.

Solution: (map[key] ?: 0) + 1, and then we write it back.

fun main() {
    val counts = mutableMapOf<String, Int>()
    val word = "kotlin"

    counts[word] = (counts[word] ?: 0) + 1
    counts[word] = (counts[word] ?: 0) + 1

    println(counts) // {kotlin=2}
}

It looks almost too simple — but that’s exactly the simplicity people love Kotlin for: everything is honest, and null is handled explicitly.

Example: a “word counter” without functional chains

It’s very tempting to start writing nicely using map/filter/groupBy, but we agreed: today, no “collection operations”, only basic tools. So we’ll make the most straightforward version: read a string, split it into words, normalize the case, and count frequencies.

We’ll count words separated by spaces. This is not a perfect text parser (punctuation will remain in words), but for practicing Map it’s great.

fun main() {
    val line = readln().trim().lowercase()
    val parts = line.split(" ")

    val counts = mutableMapOf<String, Int>()

    for (p in parts) {
        if (p != "") counts[p] = (counts[p] ?: 0) + 1
    }

    println(counts)
}

If you enter:

Kotlin kotlin  is  fun

then the output will be approximately this (the order may match insertion order):

// {kotlin=2, is=1, fun=1}

Here we saw three things at once: string normalization (trim().lowercase()), a basic for loop, and that same counter pattern.

5. Example: “category → total” and key normalization

Now let’s carefully connect Map to our practical context: we’re gradually moving toward an app that stores records (for example, expenses) in memory. No classes yet, so we’ll represent a record as Triple(category, title, amount). Today our goal is not “perfect architecture”, but map practice: we’ll build a “by categories” report.

Let’s make a function that, given a list of records, builds a MutableMap<String, Int>, where the key is a category and the value is the total spending for that category.

fun buildTotalsByCategory(
    records: List<Triple<String, String, Int>>
): Map<String, Int> {
    val totals = mutableMapOf<String, Int>()

    for (r in records) {
        val category = r.first
        val amount = r.third
        totals[category] = (totals[category] ?: 0) + amount
    }

    return totals
}

Notice a good beginner-friendly style: inside we use MutableMap because we’re “building” the result, but on the outside we return Map because we’re “returning it for reading”. It’s a simple discipline: mutate where you create, and don’t promise mutability where it isn’t needed.

Note: there’s no conversion here. MutableMap is a subtype of Map, so we return the very same object, just under a more general type, which only allows reading (remember: read-only is an API restriction, not immutability). If you keep this map and continue changing it, but want to hand out an independent snapshot, return a copy: return totals.toMap().

Let’s test it on a small example:

fun main() {
    val records = mutableListOf<Triple<String, String, Int>>()

    records.add(Triple("food", "coffee", 300))
    records.add(Triple("transport", "taxi", 1200))
    records.add(Triple("food", "sandwich", 250))

    val totals = buildTotalsByCategory(records)
    println(totals) // {food=550, transport=1200}
}

This kind of report is a core reason why Map appears in programs more often than “pure sets” and sometimes even more often than lists: a lot of tasks are “store an aggregated value by key”.

A nuance about keys: string normalization as part of the contract

Maps honestly compare keys by exact equality. For strings, that means "Food", "food" and " food " are three different keys. And that’s not a “Kotlin problem” — it’s your future responsibility as the author of the program.

So if the key comes from the user (for example, they enter a category), a good rule is to normalize it: trim() and often lowercase(). Then your Map will reflect the meaning of the data rather than random spaces and the mood of Caps Lock.

Mini example:

fun main() {
    val totals = mutableMapOf<String, Int>()

    val raw = "  FOOD "
    val category = raw.trim().lowercase()

    totals[category] = (totals[category] ?: 0) + 100

    println(totals) // {food=100}
}

If you don’t do this normalization, then categories like Food, FOOD, food will suddenly appear in the report — and it will seem like the program “broke”, when in fact it’s just taking the input too literally.

6. Common mistakes when working with Map/MutableMap

Mistake #1: expecting map[key] to return V, not V?.
Beginners often write val x: Int = prices["tea"] and are surprised by the compilation error. The compiler is your ally here: it warns you that the key may be missing. The fix is usually simple: either Elvis ?:, or a check if (key in map), or a different function contract (for example, return Int? to the caller).

Mistake #2: “fixing” V? with !!, because “I know the key exists”.
!! sometimes feels like a shortcut, but it’s like saying “I definitely won’t fall” before riding a scooter with no hands. Today you’re sure, tomorrow the input changes — and the program crashes. It’s much calmer to either check the key or use ?: with a clear default policy.

Mistake #3: trying to update a Map created via mapOf.
mapOf(...) returns a read-only Map. It’s for reading, not for changes. If you need to add/remove/update, use mutableMapOf(...). This is not nitpicking: the read-only vs mutable split is an important part of Kotlin collections design.

Mistake #4: forgetting to normalize the key and getting “entity multiplication”.
When the key is a string from the user, spaces and case become different keys. Duplicates “by meaning” appear in the report, and you start suspecting the paranormal. Usually it’s enough to agree on a rule: store keys as trim().lowercase(), and we’ll make a “pretty display” later.

Mistake #5: storing in a Map what should semantically be a List.
Sometimes you want to turn everything into a map because “key access is convenient”. But if your data is naturally ordered, if duplicates or order matter, if you need positional access — those are List tasks. Map is great when the key is truly unique and it’s logical to use it as an “address”.

Mistake #6: mixing up “key” and “index” and trying to think of a map as a list.
A Map doesn’t have a “zeroth element” or a “last index” in the way a list does. There are only keys. If you catch yourself wanting to “walk through the map by indices”, then you’re either solving an iteration task (we’ll cover it later), or you actually need a List.

1
Task
Kotlin SELF,  19 level,  3 lecture
Locked
Two order items
Two order items
1
Task
Kotlin SELF,  19 level,  3 lecture
Locked
Entry counter
Entry counter
1
Task
Kotlin SELF,  19 level,  3 lecture
Locked
Warehouse commands
Warehouse commands
Комментарии
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны sign in
This page has no comments yet