'Map default value
I'm looking for something like default value for Map.
m = new Map();
//m.setDefVal([]); -- how to write this line???
console.log(m[whatever]);
Now the result is Undefined but I want to get empty array [].
Solution 1:[1]
First of all to answer the question regarding the standard Map: Javascript Map as proposed in ECMAScript 2015 does not include a setter for default values. This, however, does not restrain you from implementing the function yourself.
If you just want to print a list, whenever m[whatever] is undefined, you can just:
console.log(m.get('whatever') || []);
as pointed out by Li357 in his comment.
If you want to reuse this functionality, you could also encapsulate this into a function like:
function getMapValue(map, key) {
return map.get(key) || [];
}
// And use it like:
const m = new Map();
console.log(getMapValue(m, 'whatever'));
If this, however, does not satisfy your needs and you really want a map that has a default value you can write your own Map class for it like:
class MapWithDefault extends Map {
get(key) {
if (!this.has(key)) {
this.set(key, this.default());
}
return super.get(key);
}
constructor(defaultFunction, entries) {
super(entries);
this.default = defaultFunction;
}
}
// And use it like:
const m = new MapWithDefault(() => []);
m.get('whatever').push('you');
m.get('whatever').push('want');
console.log(m.get('whatever')); // ['you', 'want']
Solution 2:[2]
As of 2022, Map.prototype.emplace has reached stage 2.
As it says on the proposal page, a polyfill is available in the core-js library.
Solution 3:[3]
For my purposes, I thought it would be more clear to have a DefaultMap class that extended a normal Map and added additional methods. This is really nice because it leads to more declarative code. Meaning that when you declare a new Map, not only do you declare the type of the keys and the type of the values, you also declare the default value.
As a quick example:
// Using a primitive as a default value
const myMap1 = new DefaultMap<string, number>(123);
const myMap1Value = myMap1.getAndSetDefault("some_key");
// Using a factory function to generate a default value
const myMap2 = new DefaultMap<string, number, [foo: Foo]>((_key, foo) => foo.bar);
const foo = new Foo();
const myMap2Value = myMap2.getAndSetDefault("some_key", foo);
The code is below:
type FactoryFunction<K, V, A extends unknown[]> = (k: K, ...extraArgs: A) => V;
type FirstArg<K, V, A extends unknown[]> =
| Iterable<[K, V]>
| V
| FactoryFunction<K, V, A>;
type SecondArg<K, V, A extends unknown[]> = V | FactoryFunction<K, V, A>;
interface ParsedArgs<K, V, A extends unknown[]> {
iterable: Iterable<[K, V]> | undefined;
defaultValue: V | undefined;
defaultValueFactory: FactoryFunction<K, V, A> | undefined;
}
/**
* An extended Map with some new methods:
*
* - `getAndSetDefault` - If the key exists, this will return the same thing as the `get` method.
* Otherwise, it will set a default value to the key, and then return the default value.
* - `getDefaultValue` - Returns the default value to be used for a new key. (If a factory function
* was provided during instantiation, this will execute the factory function.)
* - `getConstructorArg` - Helper method for cloning the map. Returns either the default value or
* the reference to the factory function.
*
* When instantiating a new DefaultMap, you must specify either a default value or a function that
* returns a default value.
*
* Example:
* ```ts
* // Initializes a new empty DefaultMap with a default value of "foo"
* const defaultMapWithPrimitive = new DefaultMap<string, string>("foo");
*
* // Initializes a new empty DefaultMap with a default value of a new Map
* const defaultMapWithFactory = new DefaultMap<string, Map<string, string>>(() => {
* return new Map();
* })
*
* // Initializes a DefaultMap with some initial values and a default value of "bar"
* const defaultMapWithInitialValues = new DefaultMap<string, string>([
* ["a1", "a2"],
* ["b1", "b2"],
* ], "bar");
* ```
*
* If specified, the first argument of a factory function must always be equal to the key:
*
* ```ts
* const defaultMapWithConditionalDefaultValue = new DefaultMap<number, number>((key: number) => {
* return isOdd(key) ? 0 : 1;
* });
* ```
*
* You can also specify a factory function that takes a generic amount of arguments beyond the
* first:
*
* ```ts
* const factoryFunction = (_key: string, arg2: boolean) => arg2 ? 0 : 1;
* const defaultMapWithExtraArgs = new DefaultMap<string, string, [arg2: boolean]>(factoryFunction);
* ```
*/
export class DefaultMap<K, V, A extends unknown[] = []> extends Map<K, V> {
private defaultValue: V | undefined;
private defaultValueFactory: FactoryFunction<K, V, A> | undefined;
/**
* See the DefaultMap documentation:
* [insert link here]
*/
constructor(
iterableOrDefaultValueOrDefaultValueFactory: FirstArg<K, V, A>,
defaultValueOrDefaultValueFactory?: SecondArg<K, V, A>,
) {
const { iterable, defaultValue, defaultValueFactory } = parseArguments(
iterableOrDefaultValueOrDefaultValueFactory,
defaultValueOrDefaultValueFactory,
);
if (defaultValue === undefined && defaultValueFactory === undefined) {
error(
"A DefaultMap must be instantiated with either a default value or a function that returns a default value.",
);
}
if (iterable === undefined) {
super();
} else {
super(iterable);
}
this.defaultValue = defaultValue;
this.defaultValueFactory = defaultValueFactory;
}
/**
* If the key exists, this will return the same thing as the `get` method. Otherwise, it will set
* a default value to the key, and then return the default value.
*/
getAndSetDefault(key: K, ...extraArgs: A): V {
const value = this.get(key);
if (value !== undefined) {
return value;
}
const defaultValue = this.getDefaultValue(key, ...extraArgs);
this.set(key, defaultValue);
return defaultValue;
}
/**
* Returns the default value to be used for a new key. (If a factory function was provided during
* instantiation, this will execute the factory function.)
*/
getDefaultValue(key: K, ...extraArgs: A): V {
if (this.defaultValue !== undefined) {
return this.defaultValue;
}
if (this.defaultValueFactory !== undefined) {
return this.defaultValueFactory(key, ...extraArgs);
}
return error("A DefaultMap was incorrectly instantiated.");
}
/**
* Helper method for cloning the map. Returns either the default value or a reference to the
* factory function.
*/
getConstructorArg(): V | FactoryFunction<K, V, A> {
if (this.defaultValue !== undefined) {
return this.defaultValue;
}
if (this.defaultValueFactory !== undefined) {
return this.defaultValueFactory;
}
return error("A DefaultMap was incorrectly instantiated.");
}
}
function parseArguments<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
secondArg?: SecondArg<K, V, A>,
): ParsedArgs<K, V, A> {
return secondArg === undefined
? parseArgumentsOne(firstArg)
: parseArgumentsTwo(firstArg, secondArg);
}
function parseArgumentsOne<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
): ParsedArgs<K, V, A> {
const arg = firstArg as SecondArg<K, V, A>;
const { defaultValue, defaultValueFactory } =
parseDefaultValueOrDefaultValueFactory(arg);
return {
iterable: undefined,
defaultValue,
defaultValueFactory,
};
}
function parseArgumentsTwo<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
secondArg: SecondArg<K, V, A>,
): ParsedArgs<K, V, A> {
const firstArgType = type(firstArg);
if (firstArgType !== "table") {
error(
"A DefaultMap constructor with two arguments must have the first argument be the initializer list.",
);
}
const { defaultValue, defaultValueFactory } =
parseDefaultValueOrDefaultValueFactory(secondArg);
return {
iterable: firstArg as Iterable<[K, V]>,
defaultValue,
defaultValueFactory,
};
}
function parseDefaultValueOrDefaultValueFactory<K, V, A extends unknown[]>(
arg: SecondArg<K, V, A>,
): {
defaultValue: V | undefined;
defaultValueFactory: FactoryFunction<K, V, A> | undefined;
} {
if (typeof arg === "function") {
return {
defaultValue: undefined,
defaultValueFactory: arg as FactoryFunction<K, V, A>,
};
}
if (
typeof arg === "boolean" ||
typeof arg === "number" ||
typeof arg === "string"
) {
return {
defaultValue: arg as V,
defaultValueFactory: undefined,
};
}
return error(
`A DefaultMap was instantiated with an unknown type of: ${typeof arg}`,
);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | kungfooman |
| Solution 2 | James |
| Solution 3 | James |
