If we assume that
m
represents aMap<number, V>
for a certain typeV
k
is anumber
,
how can we create an expression that
- can either retrieve an existing
V
for the keyk
, or - generate a new
v: V
, insert it into the map for the keyk
, and result inv
?
For instance, SOME_EXPRESSION(m, k, [])
should either return m.get(k)
if it already exists, or insert []
into m.set(k, [])
and return the []
.
Specific Example
Let's say we want to gradually construct a Map<number, number[]>
.
We want to assign values 100
and 200
to the key 48
, and value 300
to 52
.
We wish to create new empty arrays whenever necessary.
Therefore, we need a method like SOME_EXPRESSION(map, key, value)
that would allow us to do the following:
var m = new Map(); // Map<number, number[]>
SOME_EXPRESSION(m, 48, []).push(100)
SOME_EXPRESSION(m, 48, []).push(200)
SOME_EXPRESSION(m, 52, []).push(300)
so that the resulting map would be
{ 48 -> [100, 200]; 52 -> [300] }
What should be used instead of SOME_EXPRESSION
?
Attempts Made Thus Far
One could create a helper method:
function getOrElseUpdate(m, k, defaultValue) {
if (!m.has(k)) {
m.set(k, defaultValue);
}
return m.get(k);
}
and then utilize
SOME_EXPRESSION(m, k, []) := getOrElseUpdate(m, k, [])
. However, this method computes the hash code thrice, making it cumbersome and possibly confusing to other developers who might need to refer to the definition in a different file.
An attempt to inline this method:
SOME_EXPRESSION(m,k,v) := ((k) => (m.get(k) || ((v) => (m.set(k, v), v))(v)))(k)
resulted in the following bizarre syntax:
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, despite working, is quite peculiar.
There have been efforts to find relevant solutions, but the search has been unfortunately unproductive.
Is there a standard method to accomplish this task? (preferably in a way that is easily implemented in TypeScript)
Analogous Approaches in Other Languages
(optional; skip if you're not familiar with JVM)
In Scala, the solution might resemble the following:
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
// This results in:
//
// HashMap(
// 48 -> ListBuffer(100, 200),
// 52 -> ListBuffer(300)
// )
In Java, the approach would be quite similar:
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]}