3

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?

Martin Drozdik
  • 12,742
  • 22
  • 81
  • 146
  • Never used `Kotlin`, but after looking at your code, I would try something like `inventory["apples"] = ++inventory["apples"]!!` – SedJ601 Dec 14 '18 at 15:07
  • @Sedrick Thank you! However it does not work: https://try.kotlinlang.org/#/UserProjects/2b24n4oloe1uteuhkruoesdjid/ef89kpeif5i7sd3ls27taa8gar Kotlin is a language that uses the Java standard library. So anything that is possible in Java should be possible in Kotlin. – Martin Drozdik Dec 14 '18 at 15:12

5 Answers5

4

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.

Simulant
  • 19,190
  • 8
  • 63
  • 98
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.

Yoni Gibbs
  • 6,518
  • 2
  • 24
  • 37
  • Thank you! This works, but the implementation does two lookups under the hood. Do you think it is possible to achieve with only one lookup? – Martin Drozdik Dec 14 '18 at 15:07
  • @MartinDrozdik the computeIfPresent method does two lookups but if the map is created by `hashMapOf` then it only does one lookup. See my answer. – DodgyCodeException Dec 14 '18 at 15:38
2

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

perovic
  • 266
  • 1
  • 7
2

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.

DodgyCodeException
  • 5,963
  • 3
  • 21
  • 42
0

Another idea

// Declaration
val map = mutableMapOf<String, Int>()
// Increment
map.put("x", map.getOrPut("x"){ 1 } + 1)
Roiw
  • 147
  • 2
  • 16