Assuming that
m
is aMap<number, V>
for some typeV
k
is anumber
,
how do I write down an expression that
- either gets an already existing
V
for the keyk
, or - creates a new
v: V
, puts it into the map for the keyk
, and evaluates tov
?
For example, SOME_EXPR(m, k, [])
should either return m.get(k)
if it already exists, or put the []
into m.set(k, [])
and return the []
.
Concrete example
Suppose that I want to incrementally build a Map<number, number[]>
.
I want to assign values 100
and 200
to the key 48
, and value 300
to 52
.
I want to create new empty arrays on demand whenever needed.
For this, I need something like an SOME_EXPR(map, key, value)
such that
var m = new Map(); // Map<number, number[]>
SOME_EXPR(m, 48, []).push(100)
SOME_EXPR(m, 48, []).push(200)
SOME_EXPR(m, 52, []).push(300)
results in a map
{ 48 -> [100, 200]; 52 -> [300] }
What should one write instead of SOME_EXPR
?
What I've tried
I could, of course, create a helper method:
function getOrElseUpdate(m, k, defaultValue) {
if (!m.has(k)) {
m.set(k, defaultValue);
}
return m.get(k);
}
and then use SOME_EXPR(m, k, []) := getOrElseUpdate(m, k, [])
. But not only does it have to compute the hash code thrice, it's also just heavyweight and annoying (and possibly not obvious to the maintainer of the code, who has to click on it to see the definition in yet another file etc.).
I could try to inline this somehow:
SOME_EXPR(m,k,v) := ((k) => (m.get(k) || ((v) => (m.set(k, v), v))(v)))(k)
so that the above example would become
var m = new Map();
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(42).push(100);
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(42).push(200);
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(58).push(300);
which works, but is just bizarre.
I've also tried looking around for related answers, but this search turned out frustratingly unfruitful.
Is there any idiomatic way to achieve that? (preferably also in a way that's nicely typeable in TS)
Analogous methods from some other languages
(optional; skip this if you're not into JVM)
In Scala, it would look like somewhat like this:
val m = HashMap.empty[Int, ListBuffer[Int]]
m.getOrElseUpdate(48, ListBuffer.empty) = 100
m.getOrElseUpdate(48, ListBuffer.empty) = 200
m.getOrElseUpdate(52, ListBuffer.empty) = 300
// m is now:
//
// HashMap(
// 48 -> ListBuffer(100, 200),
// 52 -> ListBuffer(300)
// )
In Java, very similarly:
HashMap<Integer, List<Integer>> m = new HashMap<>();
m.computeIfAbsent(42, k -> new LinkedList<>()).add(100);
m.computeIfAbsent(42, k -> new LinkedList<>()).add(200);
m.computeIfAbsent(58, k -> new LinkedList<>()).add(300);
// m = {58=[300], 42=[100, 200]}
CodePudding user response:
I tried a bunch of options, but if you're simply looking for readability I personally think this is the best option, I could be wrong and would like to see something more elegant;
var m = new Map();
function SOME_EXPR(m, k, v) {
return m.get(k) || (m.set(k, v) && v);
}
function add(m, k, v) {
SOME_EXPR(m, k, []).push(v)
}
add(m, 48, 100)
add(m, 48, 200)
add(m, 52, 300)
console.log(m);
CodePudding user response:
I tried code golfing your idiomatic example down a bit:
((k, d) => m.get(k) ?? (m.set(k, d), d))(48, []).push(100);
I actually think calling a function something like getOrElseUpdate
can help serve as documentation. If the maintainer stumbles across it, it's possible s/he knows Scala and will know what it should do.
CodePudding user response:
Probably not much improvement and the drawback of spread operator, but what do you think of this?
function addValue(m, k, v) {
m.set(k, [...(m.get(k) || []), v]);
}
var map = new Map();
addValue(map, 48, 100);
addValue(map, 48, 200);
addValue(map, 52, 300);
console.log(map);