Connor
Connor

Reputation: 1026

"Variable expected" error when calling getValue on a mutable map with a nullable type

I'm having a problem with altering the value of a mutable map. I assume it's something to do with nullable types, but I'm not sure. The code producing the error is below:

fun countHexadecimalNumbers(codes: List<String>): Map<Int, Int> {
  val map = mutableMapOf<Int, Int>()
  for (s in codes) {
    val num = s.toIntOrNull(16)
    if (num != null) {
      if (num !in map.keys) {
        map[num] = 0
      }
      map.getValue(num) += 1 %<--This line causes the issue
    }
  }
  return map

When I try and build the code I get this error:

Nullable Types\Exercise 3\src\Task.kt: (13, 11): Variable expected

Does anyone have any idea why this isn't allowed?

Upvotes: 1

Views: 929

Answers (1)

Joffrey
Joffrey

Reputation: 37680

The statement map.getValue(num) += 1 is not allowed because the += operator is only defined in 2 cases:

  1. a plusAssign() operator is defined on the left operand's type (not the case for Int)
  2. a plus() operator is defined on the left operand's type AND the left operand is a variable. In this case += reassigns the variable on the left with oldValue.plus(the right operand).

The compiler tries to consider case #2 here because Int.plus(Int) is defined, but the left operand is not a variable so it fails with the error message you mentioned. You can't write this for the same reasons you can't write map.getValue(num) = 42.

The correct way of mutating a value in a map is either via the set operator (like you did earlier with the syntax sugar map[num] = 0), or via other mutating functions like merge.

In your case, merge is nice because it can remove the special case altogether (it's only available on the JVM target though):

val map = mutableMapOf<Int, Int>()
for (s in codes) {
    val num = s.toIntOrNull(16)
    if (num != null) {
        map.merge(num, 1, Int::plus)
    }
}

Here is what this merge does:

  • if the key doesn't exist in the map, it just sets the value given as second argument (the value 1)
  • if the key already exists, then a new value is computed using the function given as 3rd argument (just a sum). That function is called with the old value and the 2nd argument (the value 1) as inputs, and should return the new value to assign, so in this case old + 1.

Note that, in your case, the whole loop can be simplified this way:

fun countHexadecimalNumbers(codes: List<String>): Map<Int, Int> {
    return codes.mapNotNull { it.toIntOrNull(16) }.groupingBy { it }.eachCount()
}

Upvotes: 2

Related Questions