Say I have a map:
var inventory = mutableMapOf("apples" to 1, "oranges" to 2)
and I want to increment the number of apples by one.
However this does not work:
inventory["apples"]!!++ // Error:(9, 4) Variable expected
This neither:
var apples = inventory["apples"]!!
++apples
println(inventory["apples"]) // prints "1" => the value was not incremented.
Which is surprising, since in Kotlin, numbers are boxed when they are stored in instances of generic objects. I would not expect that a copy is being made.
It seems that the only way is to do something like:
var apples = inventory["apples"]!!
++apples
inventory["apples"] = apples
println(inventory["apples"])
Which both uses two lookups in the map and is extremely ugly and lengthy.
Is there a way to increment a value of a given key using only one lookup?
Also, could someone explain why the first two methods do not work?
There is no way to increment the value with only one get
and without a put
. You always need to do a put
afterwards, because Int
is immutable. The inc()
method (++
is a shortcut for that) returns a new value that need to be stored in the map. It does not change the stored value.
You can use
inventory.computeIfPresent("apples") { _, v -> v + 1 }
To write it in one line, but if you look into the implementation it will also do a get()
then compute the new value and do a put()
afterwards. So you can save a line of code you have to write, but you will not save CPU cycles on execution time.
Try this:
inventory.computeIfPresent("apples") { _, v -> v + 1 }
The first two methods don't work because when you retrieve a value from the map you get a primitive type, in your case Integer
, which is immutable. When you change the value of an Integer you're simply pointing your variable at a new instance of Integer, you're not updating that Integer instance which you were previously pointing at.
As Integers are boxed in maps, Integer
is a primitive wrapper class, and primitive wrapper classes are immutable in Java, it seems that you'd like to mutate an immutable object, which is unfortunately not possible, at least not by any conventional or recommended means.
Take a look at Most efficient way to increment a Map value in Java, where there are several suggestions on how to improve the performance of integer value incrementing in maps, as well as one explicitly implementing your desired behavior through a wrapper class MutableInt
, which also turns out to be the fastest (~20% faster than the baseline).
The computeIfPresent
method in java.util.HashMap only does one lookup. So you can use:
var inventory = hashMapOf("apples" to 1, "oranges" to 2)
inventory.computeIfPresent("apples") { _, v -> v + 1 } // Only 1 lookup!
Note: this is an implementation detail. It does one lookup in the current version of Kotlin (1.3.11) using a specific implementation of Java (Oracle HotSpot 11.0.1). Other configurations may or may not use different ways.