What is the best way to implement "computeIfAbsent" or "getOrElseUpdate" functionality for a Map in JavaScript?

If we assume that

  • m represents a Map<number, V> for a certain type V
  • k is a number,

how can we create an expression that

  • can either retrieve an existing V for the key k, or
  • generate a new v: V, insert it into the map for the key k, and result in v?

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]}

Answer №1

I attempted to condense your typical example through code golfing:

((k, d) => m.get(k) ?? (m.set(k, d), d))(48, []).push(100);

In my opinion, assigning a function a name like getOrElseUpdate can act as a form of documentation. If the maintainer comes across it, there is a chance that they are familiar with Scala and understand its intended functionality.

Answer №2

In my exploration of different possibilities, it seems that for the sake of readability, this particular approach stands out as the most suitable. Of course, there may be more refined solutions out there that I have yet to come across;

let storage = new Map();

function getValue(storage, key, value) {
  return storage.get(key) || (storage.set(key, value) && value);
}

function addToStorage(storage, key, value) {
  getValue(storage, key, []).push(value);
}

addToStorage(storage, 48, 100);
addToStorage(storage, 48, 200);
addToStorage(storage, 52, 300);

console.log(storage);

Answer №3

Is there any real benefit to using the spread operator in this scenario, or is it just adding complexity? What are your thoughts on this approach?

function appendToMap(map, key, value) {
    map.set(key, [...(map.get(key) || []), value]);
}

var myMap = new Map();
appendToMap(myMap, 48, 100);
appendToMap(myMap, 48, 200);
appendToMap(myMap, 52, 300);

console.log(myMap);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the most efficient and hygienic method for storing text content in JavaScript/DOM?

Typically, I encounter version 1 in most cases. However, some of the open source projects I am involved with utilize version 2, and I have also utilized version 3 previously. Does anyone have a more sophisticated solution that is possibly more scalable? V ...

Breaking down arrays using the JADE Template Engine

Currently, I am utilizing the JADE template engine in conjunction with ExpressJS. I am attempting to pass an array to my JADE template like so: var data = { "labels" : ["Label 1", "Label 2"] }; res.render('index', {data: data}); The struct ...

Getting the click event object data from a dynamically created button with jQuery or JavaScript

I have a task of tracking page button click events. Typically, I track the objects from statically created DOM elements using: $('input[type=button]').each(function () { $(this).bind('click', function () { ...

Attempting to render a container within a hidden div and then make it visible results in an error

There appears to be an issue with ExtJS 6 regarding a bug. The problem can be replicated with minimal code in this online demo. In the code snippet below, we have a hidden div: <div id="btn"></div> <div style="display:none" id="outer_contai ...

An issue arises with launching karma.js when importing node-openid-client in a TypeScript specification file

Utilizing the node-openid-client library for OpenIDConnect based authentication with an OpenID Provider. Encountering challenges while attempting to write test cases for the program. The application runs smoothly from node CLI, obtaining the code and toke ...

<JavaScript> changing the color of hyperlink in d3.js - <Organizational Chart>

screenshot The width of the link can be adjusted, but the color remains unchanged. I have attempted various solutions series.links.template.setAll({ strokeWidth: 2, strokeOpacity: 0.5, color: am5.color('#ffffff'), links: ...

Why isn't my Promise fulfilling its purpose?

Having trouble with promises, I believe I grasp the concept but it's not functioning as expected in my project. Here is a snippet of my code : (I am working with TypeScript using Angular 2 and Ionic 2) ngOnInit() { Promise.resolve(this.loadStatut ...

Experiencing a Typescript error when trying to access a property within a nested object

My current challenge involves typing an object, which seems to be error-free until I try to access a nested property and encounter the dreaded red squiggle. After some research, I came across suggestions like this: type FlagValue = string | boolean | numb ...

What is the best way to include a div element with a dynamic animation on a webpage?

I'm attempting to create a laser beam that can shoot enemies on the screen, much like in classic games such as Space Invaders or Galaga. However, I am encountering difficulties getting the laser to move when I click the button. Below is the code I hav ...

Using a function as an argument to handle the onClick event

I have a function that generates a React.ReactElement object. I need to provide this function with another function that will be triggered by an onClick event on a button. This is how I call the main function: this._createInjurySection1Drawer([{innerDra ...

How come Vue.js is not showing the image I uploaded?

Even though I can successfully print the image URL, I'm facing an issue where the img tag is not displaying it, despite my belief that I've bound it correctly. <html> <head> <title>VueJS Split Demo</title> <script t ...

Is it better to process data in a React app using Express or handle it directly on the front end with React?

Hey there, I need some advice on how to create a league table for my application. The JSON data structure is set up like this: I'm considering whether to calculate each player's league data on the front-end using React by looping through the fixt ...

Angular: Defining variables using let and var

When working with TypeScript and JavaScript, we typically use either let or var to declare a variable. However, in Angular components, we do not use them even though Angular itself uses TypeScript. For instance, export class ProductComponent implements OnI ...

Limit express to only allow AJAX requests

Currently working on an Express app where I aim to restrict access to the routes exclusively through AJAX requests. Aware that this involves using the X-Requested-With header, but uncertain of how to globally block other request types. Any suggestions or ...

What is the jQuery syntax for targeting a specific element within an object?

Is there a way to access a sub element from $(this)? For instance, how can I specifically select a span element inside the this object? ...

How can I trigger a save dialog to allow downloading a file in AngularJS?

On the server, I have a directory containing files. When a client sends a file name, I successfully retrieve the file from the server. The response from the server is working fine so far. However, after receiving the response, I want to prompt the user to ...

Tips for successfully passing multiple properties to a function in React

<DeleteForeverIcon className={classes.deleteHwIcon} onClick={() => { deleteHomework(value.name, value.class); }} /> I'm looking to modify the function deleteHomework so that it can receive two properties instead of just one. In add ...

What is the method for writing to an HTML file with the use of expressjs?

Alright, I have an interesting idea here. I am looking for a way to allow users to push a button and have a new p element permanently added to the HTML file. This means that even after reloading the page, everyone should be able to see this new element. An ...

Is it possible to access JSON with a numeric key and receive undefined as a result?

I've been attempting to extract information from my JSON data, but I keep getting an undefined result. Here is a snippet of my JSON: { "1": "A", "2": "B", "3": "C", "4": "D", "5": "E", "6": "F", "key":"pair" } This i ...

Is there a way for me to immediately send data after receiving it?

When I try to perform onPress={() => kakaoLosing() I am attempting to retrieve data (profile) from getProfile using async await and immediately dispatch that data to KAKAOLOG_IN_REQUEST, This is my current code snippet: import { ...