How to increment value of a given key with only on

2019-08-01 02:00发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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).



回答4:

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.