Map.merge() – One method to rule them all
nurkiewicz.com
nurkiewicz.com
Somewhat surprisingly, the code the author pooh-poohs is actually more performant (although it's better if the test is for the presence of the key rather than the "putIfPresent/computeIfAbsent combination) because the JVM will properly optimize a membership test (in keyset) and the use of a ternary expression makes the action clear.
There is also another variation, using map.getOrDefault()
words.forEach(word -> {
map.put(word, map.getOrDefault(word, 0) + 1);
});
and then at some points you discover the Stream API var map = words.stream().collect(Collectors.groupingBy(w -> w, Collectors.counting())); >>> from collections import defaultdict
>>> map = defaultdict(int)
>>> for word in ['hi', 'there', 'hi']:
... map[word] += 1
I am glad Java has this stuff now, but it's a little surprising that its tools are still so obtuse relative to competitors.edit: I should have foreseen the derailment caused by and nitpicking to follow a comment like this. Sorry folks!
import scala.collection.mutable.Map
> val map = Map.empty[String, Int]
> List("hi", "there", "hi").map { x =>
> map(x) = i
> }
List("hi", "there", "hi")
.zipWithIndex
.map { case (x, i) =>
x -> i
}.toMapAnd you can use Scala or Clojure with Java APIs/Libraries/Frameworks
https://www.scala-lang.org/ https://typelevel.org/cats/ http://dotty.epfl.ch/
https://clojure.org/ https://clojure.org/guides/spec https://github.com/clojure/spec-alpha2/wiki/Schema-and-selec...
Default methods on interfaces help to add features to existing collections but they are limited in what they can do, hence streams.
If you really want more ergonomic collections while staying close to Java then use Kotlin which can be easily added to most Java code bases and has all the modern functional methods like map/reduce/filter/group on standard collections.
"Modern". Lol.
I use "modern" in this context because other contemporary languages like Python/Ruby/Scala/Rust etc all have versions of those and FP-style collection usage using these names has become popular in the last 10 years.
As a user of the API, you have to wrap your code into a Stream to be lazy evaluated, the Stream being a computation monad, so you always know if a code is evaluated eagerly or lazily.
The name is ill-chosen; this mutator doesn't "merge" at all; it "updates" the map entry through a function, with a default value.
"merge" might be a somewhat good name for an operation that combines two maps, with a function that resolves key clashes; though since that is a kind of set union, that provides better terminology.
There are good uses cases for what it does - but there are also good use cases for returning the previous value, and when i have one of those, i can't use merge, and have to fall back to a get followed by a put.
Perhaps what i would really like is a Rust-style entry API, where i could use a key to obtain a Map.Entry, which i could then inspect and mutate how i liked. It might look like:
Entry<K, V> getOrCreateEntry(K key, Function<? super K, ? extends V> mappingFunction)
And then i could write: void sell(Potato) throws FarmException { ...}
Map<Field, Potato> potatoesByField = new HashMap<>();
for (var potato: getAllPotatoes()) {
var fieldAndPotato = potatoesByField.getOrCreateEntry(potato.getField(), f -> potato);
var oldPotato = fieldAndPotato.getValue();
if (potato.getWeight() > oldPotato.getWeight()) {
potatoAndField.setValue(potato);
sell(oldPotato);
} else {
sell(potato);
}
}
To collect my prize potatoes into a map, and sell the rest. Still ugly, but hey, at least it's not Go.