@cubux/readonly-map
v2.0.1
Published
Functions to work with read-only maps
Downloads
14
Maintainers
Readme
@cubux/readonly-map
A bunch of helper functions to work with read-only maps. Works internally with native maps without any kind of magic.
import { remove, updateDefault, updateDefaultDeep } from '@cubux/readonly-map';
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
console.log(updateDefault(input, 'z', 30, (v) => v + 1));
// => Map(3) { 'x' => 10, 'y' => 20, 'z' => 31 }
console.log(remove(input, 'x'));
// => Map(1) { 'y' => 20 }
const nested: ReadonlyMap<string, ReadonlyMap<number, number>> = new Map([
['x', new Map([
[10, 23],
[20, 42],
])],
]);
updateDefaultDeep(nested, ['x', 20], 0, (v) => v + 1);
// => Map(1) {
// 'x' => Map(2) { 10 => 23, 20 => 43 },
// }
updateDefaultDeep(nested, ['y', 30], 0, (v) => v + 1);
// => Map(2) {
// 'x' => Map(2) { 10 => 23, 20 => 42 },
// 'y' => Map(1) { 30 => 1 },
// }
Alternative usage:
import * as RoMap from '@cubux/readonly-map';
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
console.log(RoMap.remove(input, 'x'));
// => Map(1) { 'y' => 20 }
Use Cases
State management
import { FC, useState, ChangeEvent } from 'react';
import { remove, set } from '@cubux/readonly-map';
const TodoList: FC = () => {
const [values, setValues] = useState<ReadonlyMap<string, number>>(() => new Map());
const handleChangeItem = (
key: string,
{ target: { value } }: ChangeEvent<HTMLInputElement>,
) =>
setValues(prev => value ? set(prev, key, value) : remove(prev, key));
...
};
Install
npm i @cubux/readonly-map
API
every()
every(
map: ReadonlyMap<K, V>,
predicate: (value: V, key: K, map: ReadonlyMap<K, V>) => unknown,
): boolean
Tests whether all elements in the map pass the test implemented by the
provided function. Works similar to Array.prototype.every()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
every(input, (v) => v % 10 === 0);
// => true
every(input, (v) => v % 20 === 0);
// => false
every(input, (_, k) => k === k.toLowerCase());
// => true
See also map()
, some()
.
filter()
filter(
map: ReadonlyMap<K, V>,
predicate: (value: V, key: K, map: ReadonlyMap<K, V>) => unknown,
): ReadonlyMap<K, V>
Creates a new map with all elements that pass the test implemented by the
provided function. Works similar to Array.prototype.filter()
.
- Will return input
map
if nothing changed (t.i. all elements passed the test).
const input: ReadonlyMap<string, number> = new Map([
['x', 10],
['y', 20],
['Z', 42],
]);
filter(input, (v, k) => v % 20 === 0 || k === k.toUpperCase());
// => Map(2) { 'y' => 20, 'Z' => 42 }
See also map()
.
find()
find(
map: ReadonlyMap<K, V>,
predicate: (value: V, key: K, map: ReadonlyMap<K, V>) => unknown,
): V | undefined
Returns the value of the first element in the provided map that satisfies
the provided testing function. If no elements satisfy the testing function,
undefined
is returned. Works similar to Array.prototype.find()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
find(input, (v) => v % 20 === 0);
// => 20
find(input, (v) => v % 10 === 0);
// => 10
find(input, (v) => !v);
// => undefined
find(input, (_, k) => k === k.toUpperCase());
// => undefined
See also findKey()
, includes()
, some()
.
findKey()
findKey(
map: ReadonlyMap<K, V>,
predicate: (value: V, key: K, map: ReadonlyMap<K, V>) => unknown,
): K | undefined
Returns the key of the first element in the provided map that satisfies
the provided testing function. If no elements satisfy the testing function,
undefined
is returned. Works similar to Array.prototype.findIndex()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
findKey(input, (v) => v % 20 === 0);
// => 'y'
findKey(input, (v) => v % 10 === 0);
// => 'x'
findKey(input, (v) => !v);
// => undefined
findKey(input, (_, k) => k === k.toUpperCase());
// => undefined
See also find()
.
fromArray()
fromArray(
array: readonly T[],
calcKey: (item: T) => K,
): ReadonlyMap<K, T>
Creates a map from simple array of items, calculating corresponding key for
every item with the given callback calcKey()
.
fromArray([1, 2, 3], (v) => v * 10)
// => Map(3) { 10 => 1, 20 => 2, 30 => 3 }
This is a shortcut for commonly used operation:
new Map(array.map(v => [calcKey(v), v]))
getOr()
getOr(
map: ReadonlyMap<K, V>,
key: K,
defaultValue: D
): V | D
Get a value of the given key in the map. If map has no given key a
defaultValue
is returned.
const input: ReadonlyMap<string, number | undefined> = new Map([
['x', 10],
['y', undefined],
]);
getOr(input, 'x');
// => 10
getOr(input, 'y', null);
// => undefined
getOr(input, 'z', null);
// => null
See also getDeep()
.
getDeep()
getDeep(
map: MapDeep<KS, V>,
path: KS,
defaultValue: D,
): V | D
Returns a value from the given nested maps structure referenced with given
keys path. If map has no final element in the given path a defaultValue
is
returned.
- An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
['y', new Map()],
]);
getDeep(input, ['x', 'b']);
// => 20
getDeep(input, ['x']);
// => Map(2) { 'a' => 10, 'b' => 20 }
getDeep(input, ['y', 't']);
// => undefined
getDeep(input, ['y', 't'], null);
// => null
See also getOr()
, hasDeep()
.
hasDeep()
hasDeep(
map: MapDeep<KS, any>,
path: KS,
): boolean
Returns a boolean indicating whether an element with the specified keys path exists or not.
- An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
['y', new Map()],
]);
hasDeep(input, ['x', 'b']);
// => true
hasDeep(input, ['x']);
// => true
hasDeep(input, ['y', 't']);
// => false
See also getDeep()
.
includes()
includes(
map: ReadonlyMap<K, V>,
value: V,
): boolean
Determines whether a map includes a certain value among its values,
returning true
or false
as appropriate. Works similar to
Array.prototype.includes()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
includes(input, 20);
// => true
includes(input, 42);
// => false
See also find()
, some()
.
map()
map(
map: ReadonlyMap<K, V>,
callback: (value: V, key: K, map: ReadonlyMap<K, V>) => U,
): ReadonlyMap<K, U>
Creates a new map populated with the results of calling a provided function
on every element in the calling map as values with respective keys from input
map. Works similar to Array.prototype.map()
.
- Will return input
map
when nothing to change (t.i. inputmap
is empty or every new value is identical to respective old value).
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
map(input, (v) => v + 1);
// => Map(2) { 'x' => 11, 'y' => 21 }
map(input, (v, k) => ({[k]: v}));
// => Map(2) { 'x' => { x: 10 }, 'y' => { y: 20 } }
See also reduce()
.
merge()
merge(
a: ReadonlyMap<K, V>,
...b: ReadonlyMap<K, V>[]
): ReadonlyMap<K, V>
Merge multiple maps into new map. Every key in the returned map will have the latest value from all occurrences of the key.
- May return input map when nothing to change.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
merge(input, new Map([['z', 30], ['y', 42]]));
// => Map(3) { 'x' => 10, 'y' => 42, 'z' => 30 }
merge(input, new Map([['y', 20]])) === input;
// => true
merge(new Map(), input, new Map()) === input;
// => true
See also map()
, set()
.
toObjectDeep()
toObjectDeep(
map: ReadonlyMap<keyof any, any>,
): object
Converts nested maps structure into nested objects keeping key=>value pairs.
const input: ReadonlyMap<string, ReadonlyMap<number, string>> = new Map([
['x', new Map([
[10, 'a'],
[20, 'b'],
])],
['y', new Map([[30, 'c']])],
['z', new Map()],
]);
toObjectDeep(map);
// {
// x: {
// 10: 'a',
// 20: 'b',
// },
// y: {
// 30: 'c',
// },
// z: {},
// }
See also reduce()
.
reduce()
reduce(
map: ReadonlyMap<K, V>,
reducer: (prev: U, value: V, key: K, map: ReadonlyMap<K, V>) => U,
initial: U,
): U
Executes a provided reducer function on each element of the map, resulting
in a single output value. Works similar to Array.prototype.reduce()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
reduce(input, (s, v) => s + v, 0);
// => 30
reduce(input, (m, v, k) => m.set(v, k), new Map<number, string>())
// => Map(2) { 10 => 'x', 20 => 'y' }
See also map()
.
remove()
remove(
map: ReadonlyMap<K, V>,
key: K,
): ReadonlyMap<K, V>
Create a new map without given key.
- Will return input
map
when it doesn't have givenkey
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
remove(input, 'x');
// => Map(1) { 'y' => 20 }
See also removeDeep()
, filter()
, reduce()
.
removeDeep()
removeDeep(
map: MapDeep<KS, V>,
path: KS,
): MapDeep<KS, V>
Creates new nested maps structure from input
by removing a single element
with the given keys path
.
- Will return input
map
when element with the given path doesn't exist. - An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
['y', new Map()],
]);
removeDeep(input, ['x', 'b']);
// => Map(2) {
// 'x' => Map(2) { 'a' => 10 },
// 'y' => Map(0) {},
// }
See also remove()
, hasDeep()
, setDeep()
.
set()
set(
map: ReadonlyMap<K, V>,
key: K,
value: V,
): ReadonlyMap<K, V>
Creates new map with the given key assigned to the given value.
- Will return input
map
then nothing to change (t.i. inputmap
already hasvalue
assigned tokey
).
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
set(input, 'z', 40);
// => Map(3) { 'x' => 10, 'y' => 20, 'z' => 40 }
set(input, 'y', 42);
// => Map(2) { 'x' => 10, 'y' => 42 }
set(input, 'y', 20) === input;
// => true
See also setDeep()
, update()
, updateDefault()
.
setDeep()
setDeep(
map: MapDeep<KS, V>,
path: KS,
value: V,
): MapDeep<KS, V>
Creates new nested maps structure from input
by assigning the given value
to a single element with the given keys path
.
- Will return input
map
when element with the given path already exists and is identical to the givenvalue
already. - An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
]);
setDeep(input, ['x', 'b'], 42);
// => Map(1) {
// 'x' => Map(2) { 'a' => 10, 'b' => 42 },
// }
setDeep(input, ['y'], new Map([['c' => 42]]));
// => Map(2) {
// 'x' => Map(2) { 'a' => 10, 'b' => 20 },
// 'y' => Map(2) { 'c' => 42 },
// }
setDeep(input, ['x', 'b'], 20) === input;
// => true
See also set()
, updateDeep()
, updateDefaultDeep()
.
some()
some(
map: ReadonlyMap<K, V>,
predicate: (value: V, key: K, map: ReadonlyMap<K, V>) => unknown,
): boolean
Tests whether at least one element in the map passes the test implemented by
the provided function. It returns true
if it finds an element for which the
provided function returns truthy value; otherwise it returns false
. Works
similar to Array.prototype.some()
.
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['Y', 20]]);
some(input, (v) => v % 10 === 0);
// => true
some(input, (v) => v % 20 === 0);
// => true
some(input, (_, k) => k === k.toUpperCase());
// => true
some(input, (v) => v % 2);
// => false
See also every()
, find()
, includes()
.
update()
update(
map: ReadonlyMap<K, V>,
key: K,
updater: (prev: V, key: K, map: ReadonlyMap<K, V>) => V,
): ReadonlyMap<K, V>
Creates new map from input map
by updating value in the given key
with
the given callback updater()
.
- Will do nothing and return input
map
ifkey
does not exist. See elseupdateDefault()
as opposite for this case. - Will return input
map
when nothing to change (t.i. when new value returned fromupdater()
is identical to old value).
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
update(input, 'y', (v) => v + 1);
// => Map(2) { 'x' => 10, 'y' => 21 }
update(input, 'z', () => { throw new Error("Won't be called"); }) === input;
// => true
See also set()
, updateDefault()
, updateDeep()
.
updateDeep()
updateDeep(
map: MapDeep<KS, V>,
path: KS,
updater: (value: V) => V,
): MapDeep<KS, V>
Creates new nested maps structure from input map
by updating value in the
given keys path with the given callback updater()
.
- Will do nothing and return input
map
if element in the given path does not exist. See elseupdateDefaultDeep()
as opposite for this case. - Will return input
map
when nothing to change (t.i. when new value returned fromupdater()
is identical to old value). - An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
]);
updateDeep(input, ['x', 'b'], (v) => v + 1);
// => Map(1) {
// 'x' => Map(2) { 'a' => 10, 'b' => 21 },
// }
updateDeep(input, ['y'], () => { throw new Error("Won't be called") }) === input;
// => true
updateDeep(input, ['x', 'b'], (v) => v) === input;
// => true
See also update()
, updateDefaultDeep()
, setDeep()
.
updateDefault()
updateDefault(
map: ReadonlyMap<K, V>,
key: K,
defaultValue: V | D,
updater: (prev: V | D, key: K, map: ReadonlyMap<K, V>) => V,
): ReadonlyMap<K, V>
Creates new map from input map
by updating value in the given key
with
the given callback updater()
. The given defaultValue
will be used as
"old" value for the callback when then given key
doesn't exist in the given
map
.
- Will always call
updater()
callback. See elseupdate()
as opposite for this case. - Will return input
map
when nothing to change (t.i. when new value returned fromupdater()
is identical to old value wherever it came from - existing value ofkey
ordefaultValue
).
const input: ReadonlyMap<string, number> = new Map([['x', 10], ['y', 20]]);
updateDefault(input, 'y', 30, (v) => v + 1);
// => Map(2) { 'x' => 10, 'y' => 21 }
updateDefault(input, 'z', 30, (v) => v + 1);
// => Map(3) { 'x' => 10, 'y' => 20, 'z' => 31 }
updateDefault(input, 'z', 0, (v) => v) === input;
// => true
See also set()
, update()
, updateDefaultDeep()
.
updateDefaultDeep()
updateDefaultDeep(
map: MapDeep<KS, V>,
path: KS,
defaultValue: V,
updater: (value: V) => V,
): MapDeep<KS, V>
Creates new nested maps structure from input map
by updating value in the
given keys path with the given callback updater()
.
- Will always call
updater()
callback. See elseupdateDeep()
as opposite for this case. - Will return input
map
when nothing to change (t.i. when new value returned fromupdater()
is identical to old value wherever it came from - existing value ofkey
ordefaultValue
). - An empty path
[]
cause an error since function was designed for nested maps with fixed depth.
const input: ReadonlyMap<string, ReadonlyMap<string, number>> = new Map([
['x', new Map([
['a', 10],
['b', 20],
])],
]);
updateDefaultDeep(input, ['x', 'b'], 30, (v) => v + 1);
// => Map(1) {
// 'x' => Map(2) { 'a' => 10, 'b' => 21 },
// }
updateDefaultDeep(input, ['y', 'c'], 30, (v) => v + 1);
// => Map(2) {
// 'x' => Map(2) { 'a' => 10, 'b' => 20 },
// 'y' => Map(2) { 'c' => 31 },
// }
updateDefaultDeep(input, ['x', 'b'], 0, (v) => v) === input;
// => true
updateDefaultDeep(input, ['y', 'c'], 0, (v) => v) === input;
// => true
See also updateDefault()
, updateDeep()
, setDeep()
.