lodash generate deep object with collection data - javascript

Hi suppose i have access to those data ref
const GlobalDataA = { uid0: {}, uid1: {}, uid2: {} };
const GlobalDataB = { uid3: {}, uid4: {}, uid5: {} };
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
how i can easily with Lodash generate a new collections object with keys uid like this
{
uid0: { uid3: [0, 1, 2, 3], uid4: [0, 1, 2, 3] },
uid1: { uid3: [0, 1, 2, 3], uid4: [0, 1, 2, 3] },
}
i know how proceed to generate a collections array
const test = mapperA.map((A) => mapperB.map((B) => _.range(range).map((value, i) => i)));
it will give me this
but my target is to generate a new object with uid key like this
ideally a 1 or 2 line formula to stay clean will be great !
I try pick and zipObject from loadash without success !
thanks

Return pairs of [key, value] from the _.map() calls, and convert to objects using _.fromPairs():
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
const result = _.fromPairs(mapperA.map((A) =>
[
A,
_.fromPairs(mapperB.map((B) => [
B,
_.range(range)
]))
])
);
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
With vanilla JS, you can replace _.fromPairs() with Object.fromEntries(), and generate the range with Array.from():
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
const result = Object.fromEntries(mapperA.map((A) =>
[
A,
Object.fromEntries(mapperB.map((B) => [
B,
Array.from({ length: range }, (_, i) => i)
]))
])
);
console.log(result)

Related

How can I return a html element by mapping a Map object like Array.prototype.map() does? [duplicate]

How would you do this? Instinctively, I want to do:
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
// wishful, ignorant thinking
var newMap = myMap.map((key, value) => value + 1); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
I've haven't gleaned much from the documentation on the new iteration protocol.
I am aware of wu.js, but I'm running a Babel project and don't want to include Traceur, which it seems like it currently depends on.
I also am a bit clueless as to how to extract how fitzgen/wu.js did it into my own project.
Would love a clear, concise explanation of what I'm missing here. Thanks!
Docs for ES6 Map, FYI
So .map itself only offers one value you care about...
That said, there are a few ways of tackling this:
// instantiation
const myMap = new Map([
[ "A", 1 ],
[ "B", 2 ]
]);
// what's built into Map for you
myMap.forEach( (val, key) => console.log(key, val) ); // "A 1", "B 2"
// what Array can do for you
Array.from( myMap ).map(([key, value]) => ({ key, value })); // [{key:"A", value: 1}, ... ]
// less awesome iteration
let entries = myMap.entries( );
for (let entry of entries) {
console.log(entry);
}
Note, I'm using a lot of new stuff in that second example...
...Array.from takes any iterable (any time you'd use [].slice.call( ), plus Sets and Maps) and turns it into an array... ...Maps, when coerced into an array, turn into an array of arrays, where el[0] === key && el[1] === value; (basically, in the same format that I prefilled my example Map with, above).
I'm using destructuring of the array in the argument position of the lambda, to assign those array spots to values, before returning an object for each el.
If you're using Babel, in production, you're going to need to use Babel's browser polyfill (which includes "core-js" and Facebook's "regenerator").
I'm quite certain it contains Array.from.
Just use Array.from(iterable, [mapFn]).
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newEntries = Array.from(myMap, ([key, value]) => [key, value + 1]);
var newMap = new Map(newEntries);
You should just use Spread operator:
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newArr = [...myMap].map(value => value[1] + 1);
console.log(newArr); //[2, 3, 4]
var newArr2 = [for(value of myMap) value = value[1] + 1];
console.log(newArr2); //[2, 3, 4]
You can use this function:
function mapMap(map, fn) {
return new Map(Array.from(map, ([key, value]) => [key, fn(value, key, map)]));
}
usage:
var map1 = new Map([["A", 2], ["B", 3], ["C", 4]]);
var map2 = mapMap(map1, v => v * v);
console.log(map1, map2);
/*
Map { A → 2, B → 3, C → 4 }
Map { A → 4, B → 9, C → 16 }
*/
Using Array.from I wrote a Typescript function that maps the values:
function mapKeys<T, V, U>(m: Map<T, V>, fn: (this: void, v: V) => U): Map<T, U> {
function transformPair([k, v]: [T, V]): [T, U] {
return [k, fn(v)]
}
return new Map(Array.from(m.entries(), transformPair));
}
const m = new Map([[1, 2], [3, 4]]);
console.log(mapKeys(m, i => i + 1));
// Map { 1 => 3, 3 => 5 }
Actually you can still have a Map with the original keys after converting to array with Array.from. That's possible by returning an array, where the first item is the key, and the second is the transformed value.
const originalMap = new Map([
["thing1", 1], ["thing2", 2], ["thing3", 3]
]);
const arrayMap = Array.from(originalMap, ([key, value]) => {
return [key, value + 1]; // return an array
});
const alteredMap = new Map(arrayMap);
console.log(originalMap); // Map { 'thing1' => 1, 'thing2' => 2, 'thing3' => 3 }
console.log(alteredMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
If you don't return that key as the first array item, you loose your Map keys.
In typescript, in case somebody would need it :
export {}
declare global {
interface Map<K, V> {
map<T>(predicate: (key: K, value: V) => T): Map<V, T>
}
}
Map.prototype.map = function<K, V, T>(predicate: (value: V, key: K) => T): Map<K, T> {
let map: Map<K, T> = new Map()
this.forEach((value: V, key: K) => {
map.set(key, predicate(value, key))
})
return map
}
You can map() arrays, but there is no such operation for Maps. The solution from Dr. Axel Rauschmayer:
Convert the map into an array of [key,value] pairs.
Map or filter the array.
Convert the result back to a map.
Example:
let map0 = new Map([
[1, "a"],
[2, "b"],
[3, "c"]
]);
const map1 = new Map(
[...map0]
.map(([k, v]) => [k * 2, '_' + v])
);
resulted in
{2 => '_a', 4 => '_b', 6 => '_c'}
I prefer to extend the map
export class UtilMap extends Map {
constructor(...args) { super(args); }
public map(supplier) {
const mapped = new UtilMap();
this.forEach(((value, key) => mapped.set(key, supplier(value, key)) ));
return mapped;
};
}
You can use myMap.forEach, and in each loop, using map.set to change value.
myMap = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
for (var [key, value] of myMap.entries()) {
console.log(key + ' = ' + value);
}
myMap.forEach((value, key, map) => {
map.set(key, value+1)
})
for (var [key, value] of myMap.entries()) {
console.log(key + ' = ' + value);
}
const mapMap = (callback, map) => new Map(Array.from(map).map(callback))
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newMap = mapMap((pair) => [pair[0], pair[1] + 1], myMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
If you don't want to convert the entire Map into an array beforehand, and/or destructure key-value arrays, you can use this silly function:
/**
* Map over an ES6 Map.
*
* #param {Map} map
* #param {Function} cb Callback. Receives two arguments: key, value.
* #returns {Array}
*/
function mapMap(map, cb) {
let out = new Array(map.size);
let i = 0;
map.forEach((val, key) => {
out[i++] = cb(key, val);
});
return out;
}
let map = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(
mapMap(map, (k, v) => `${k}-${v}`).join(', ')
); // a-1, b-2, c-3
Map.prototype.map = function(callback) {
const output = new Map()
this.forEach((element, key)=>{
output.set(key, callback(element, key))
})
return output
}
const myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]])
// no longer wishful thinking
const newMap = myMap.map((value, key) => value + 1)
console.info(myMap, newMap)
Depends on your religious fervor in avoiding editing prototypes, but, I find this lets me keep it intuitive.
Here is a Typescript variant which builds upon some of the existing ideas and attempts to be flexible in the following ways:
Both key and value types of output map can differ from input
The mapper function can access the input map itself, if it likes
function mapMap<TKI, TVI, TKO, TVO>(map: Map<TKI, TVI>,
f: (k: TKI, v: TVI, m: Map<TKI, TVI>) => [TKO, TVO]) : Map<TKO, TVO>{
return new Map([...map].map(p => f(p[0], p[1], map)))
}
Note: this code uses the spread operator, like some of the existing ideas, and so needs a target of of 'es2015' or higher according to my VS Code Intellisense.
Maybe this way:
const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map((k, v) => [k, v * 2]); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }
You would only need to monkey patch Map before:
Map.prototype.map = function(func){
return new Map(Array.from(this, ([k, v]) => func(k, v)));
}
We could have wrote a simpler form of this patch:
Map.prototype.map = function(func){
return new Map(Array.from(this, func));
}
But we would have forced us to then write m.map(([k, v]) => [k, v * 2]); which seems a bit more painful and ugly to me.
Mapping values only
We could also map values only, but I wouldn't advice going for that solution as it is too specific.
Nevertheless it can be done and we would have the following API:
const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map(v => v * 2); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }
Just like before patching this way:
Map.prototype.map = function(func){
return new Map(Array.from(this, ([k, v]) => [k, func(v)]));
}
Maybe you can have both, naming the second mapValues to make it clear that you are not actually mapping the object as it would probably be expected.

Is there a `.map` like function for objects? To create a new object with the same keys

With an array we can use the .map method to create a new map with the same array structure (ie. the same number of elements).
eg.
const array = [2, 4, 6];
const newArray = array.map(v => v *2); //[4, 8, 12];
I believe in functional programming this makes the array object what is called a functor.
With objects I want to do something similar - I want to create a new object with the same structure (same keys), as well as the same functions etc as the original object.
eg.
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = obj.map(v => v *2); // {foo: 4, bar: 8, biz: 12}
The way I'm currently doing this is by using Object.entries and .reduce:
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = Object.entries(obj).reduce((acc,cur) => {
return {
...acc,
[cur[0]]: cur[1] * 2
}
}, {});
console.log(newObj);
I'm wondering - is there currently an object method that would let me do this that I'm missing? Or a proposal to add one?
The map function for objects is easy enough to define.
Object.defineProperty(Object.prototype, "map", {
value(mapping) {
const oldEntries = Object.entries(this);
const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]);
return Object.fromEntries(newEntries);
}
});
const obj = { foo: 2, bar: 4, baz: 6 };
const result = obj.map(x => 2 * x);
console.log(result);
Note that this is different from kornieff's objMap function because the mapping function can't access or change the key. Hence, it's the correct implementation of the Functor type class for objects.
As a bonus, let's implement some other useful type classes for objects. First, the Representable type class.
Object.tabulate = table => new Proxy({}, {
get: (_, key) => table(key)
});
const object = Object.tabulate(key => {
if (key === "foo") return 10;
if (key === "bar") return 20;
return 30;
});
console.log(object.foo); // 10
console.log(object.bar); // 20
console.log(object.baz); // 30
The tabulate function can be used to define various useful type classes such as Monad and Comonad. For example, let's implement the Distributive type class. I'll leave the implementation of the other type classes as an exercise for the reader.
Object.tabulate = table => new Proxy({}, {
get: (_, key) => table(key)
});
const distribute = functor => Object.tabulate(key =>
functor.map(object => object[key]));
const result1 = distribute([ { foo: 10, bar: 20 }, { foo: 30, bar: 40 } ]);
console.log(result1.foo); // [ 10, 30 ]
console.log(result1.bar); // [ 20, 40 ]
Object.defineProperty(Object.prototype, "map", {
value(mapping) {
const oldEntries = Object.entries(this);
const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]);
return Object.fromEntries(newEntries);
}
});
const result2 = distribute({ a: { foo: 10, bar: 20 }, b: { foo: 30, bar: 40 } });
console.log(result2.foo); // { a: 10, b: 30 }
console.log(result2.bar); // { a: 20, b: 40 }
Note that distribute expects an object that has a map method, which is why we defined the map method on Object.prototype. However, we could have used the Yoneda lemma to get around this restriction.
There is no native map for objects, but can easily make one. I think entries is the simplest.
const obj = { foo: 2, bar: 4, biz: 6 }
const objMap = (obj, fn) =>
Object.fromEntries(
Object.entries(obj).map(
([k, v], i) => [k, fn(v, k, i)]
)
)
console.log(
objMap(obj, v => v * 2),
objMap(obj, (v, k) => `${k}-${v}`),
objMap(obj, (v, _, i) => v * i)
)
One way to do it:
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const x = Object.keys(obj).map(o => {
return {[o]: obj[o] * 2}
})
console.log(JSON.stringify(x, null, 2))
Another way to do it using .reduce() is like this, making use of destructuring to read a little bit easier ( I think )
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = Object.entries(obj).reduce(
(acc, [key, value]) => ({...acc, [key]: value * 2}), {}
);
console.log(newObj)
If you fancy experimenting a bit of functional apis in your code,
then you could consider to use a functional library (such as Ramda),
It doesn't only introduce the functor api for objects (map), but also other cool stuff!
const input = { a: 1, b: 44, c: 77 };
const doubled = R.map(R.multiply(2), input);
console.log(
`doubling input`,
doubled,
);
// and a lot more...
const odds = R.filter(R.modulo(R.__, 2), input);
console.log(
`filtering odds only of input`,
odds,
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
you can use the function mapValues of lodash
const array = [2, 4, 6];
_.mapValues(array, v => v * 2)

RXJS - merge two stream results into one sorted array

I have two streams. Let's say:
const firstStream = Rx.of([
{
first: 'first',
}, {
third: 'third',
}
]);
const secondStream = Rx.of([
{
second: 'second'
}, {
fourth: 'fourth'
}
]);
Now I want a stream that combines the result of these two streams and maps the result array sorted as follows:
const resultArr = [
{
first: 'first',
},
{
second: 'second'
},
{
third: 'third',
},
{
fourth: 'fourth'
}
];
I tried to use combineLatest with RxJS flatmap operator, but that did not work out. I provided a stackblitz playground to test around: StackBlitz
I'm sure there are plenty of ways to do this. Maybe someone can help me out :)
const { from, merge } = rxjs;
const { reduce, map, mergeMap } = rxjs.operators
const a = from(['first', 'third']);
const b = from(['second', 'fourth']);
const sortMap = {
first: 0,
second: 1,
third: 2,
fourth: 4,
}
merge(a, b).pipe(
// wait until every observable has completed,
// zip all the values into an array
reduce((res, item) => res.concat(item), []),
// sort the array accordingly to your needs
map(list => list.sort((a, b) => sortMap[a] - sortMap[b])),
// flatten the array into a sequence
mergeMap(list => list),
).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js" integrity="sha256-mNXCdYv896VtdKYTBWgurbyH+p9uDUgWE4sYjRnB5dM=" crossorigin="anonymous"></script>
enter code hereAs you said your streams first complete and after that, you need the sorted value as single output of the stream, so I would recommend the forkJoin operator, which operator will Wait for Observables to complete and then combine last values they emitted.
const { of, forkJoin } = rxjs;
const { map } = rxjs.operators;
let a$ = of([1, 8, 10, 4]);
let b$ = of([3, 5, 43, 0]);
forkJoin(a$, b$)
.pipe(
map(([a, b]) => [...a, ...b]),
map(x => x.sort((a, b) => a - b))
)
.subscribe(x => {
console.log('Sorted =>', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js" integrity="sha256-mNXCdYv896VtdKYTBWgurbyH+p9uDUgWE4sYjRnB5dM=" crossorigin="anonymous"></script>

Two object arrays: merge with same key

I have two object arrays. I want to merge with key with value
var a = [{"fit":["34","32","30","28"],"size":["x"]}]
var b = [{"size":["s","m","xl"],"fit":["36"]}]
Expected Output should be
Obj=[{"fit":["34","32","30","28","36"],"size":["x,"s","m","xl"]}]
My Code is
let arr3 = [];
b.forEach((itm, i) => {
arr3.push(Object.assign({}, itm, a[i]));
});
alert(JSON.stringify(arr3))
it gives [{"size":["x"],"fit":["34","32","30","28"]}] which wrong.
Use Array.reduce().
// Combine into single array (spread operator makes this nice)
const myArray = [...a, ...b];
// "reduce" values in array down to a single object
const reducedArray = myArray.reduce((acc, val) => {
return [{fit: [...acc.fit, ...val.fit], size: [...acc.size, ...val.size]}];
});
Edit: if you want the reducer to merge objects regardless of what keys and fields it has then you can do by iterating over the keys of the objects and merging them dynamically:
const reducedArray = myArray.reduce((acc, val) => {
const returnObject = {};
for (const eaKey in acc) {
returnObject[eaKey] = [...acc[eaKey], ...val[eaKey]];
}
return [returnObject];
});
If the fields of the objects aren't guaranteed keys then you will need to get even more dynamic in detecting the type of merge and how to do it, but it's possible and I will leave that as an exercise for you to figure out. :)
Note that if there are duplicate values in each of the "fit" and "size" arrays, they will not be deduplicated. You'd have to do that manually as a separate step either with extra logic in the reduce function or afterwards.
combine a and b in a single array then reduce it starting with an array having an object with empty fit and size arrays:
var a = [{ fit: ["34", "32", "30", "28"], size: ["x"] }];
var b = [{ size: ["s", "m", "xl"], fit: ["36"] }];
var obj = [...a, ...b].reduce(
(acc, curr) => {
Object.keys(curr).forEach(k => {
acc[0][k] = [...new Set([...(acc[0][k] || []), ...curr[k]])];
});
return acc;
},
[{}]
);
console.log(obj);
You can create a combine function that takes fit and size from any two objects and merges them.
Use it as a reducer to combine everything.
let combine = ({fit, size}, {fit: fit2, size: size2}) =>
({ fit: [...fit, ...fit2], size: [...size, ...size2] });
let result = [...a, ...b].reduce(combine);
Example:
var a = [{"fit":["34","32","30","28"],"size":["x"]}, {"fit": ["10", "11"], "size":["xxxxxxxxl"]}]
var b = [{"size":["s","m","xl"],"fit":["36"]}];
let combine = ({fit, size}, {fit: fit2, size: size2}) =>
({ fit: [...fit, ...fit2], size: [...size, ...size2] });
let result = [...a, ...b].reduce(combine);
console.log(result);
If you don't want to use the keys directly you could try
const arr3 = b.reduce((carry, current, index) => {
Object.keys(current)
.forEach(key => {
Object.assign(carry, { [key]: Array.prototype.concat.call(current[key], a[index][key])});
});
return carry;
}, {});

.map() a Javascript ES6 Map?

How would you do this? Instinctively, I want to do:
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
// wishful, ignorant thinking
var newMap = myMap.map((key, value) => value + 1); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
I've haven't gleaned much from the documentation on the new iteration protocol.
I am aware of wu.js, but I'm running a Babel project and don't want to include Traceur, which it seems like it currently depends on.
I also am a bit clueless as to how to extract how fitzgen/wu.js did it into my own project.
Would love a clear, concise explanation of what I'm missing here. Thanks!
Docs for ES6 Map, FYI
So .map itself only offers one value you care about...
That said, there are a few ways of tackling this:
// instantiation
const myMap = new Map([
[ "A", 1 ],
[ "B", 2 ]
]);
// what's built into Map for you
myMap.forEach( (val, key) => console.log(key, val) ); // "A 1", "B 2"
// what Array can do for you
Array.from( myMap ).map(([key, value]) => ({ key, value })); // [{key:"A", value: 1}, ... ]
// less awesome iteration
let entries = myMap.entries( );
for (let entry of entries) {
console.log(entry);
}
Note, I'm using a lot of new stuff in that second example...
...Array.from takes any iterable (any time you'd use [].slice.call( ), plus Sets and Maps) and turns it into an array... ...Maps, when coerced into an array, turn into an array of arrays, where el[0] === key && el[1] === value; (basically, in the same format that I prefilled my example Map with, above).
I'm using destructuring of the array in the argument position of the lambda, to assign those array spots to values, before returning an object for each el.
If you're using Babel, in production, you're going to need to use Babel's browser polyfill (which includes "core-js" and Facebook's "regenerator").
I'm quite certain it contains Array.from.
Just use Array.from(iterable, [mapFn]).
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newEntries = Array.from(myMap, ([key, value]) => [key, value + 1]);
var newMap = new Map(newEntries);
You should just use Spread operator:
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newArr = [...myMap].map(value => value[1] + 1);
console.log(newArr); //[2, 3, 4]
var newArr2 = [for(value of myMap) value = value[1] + 1];
console.log(newArr2); //[2, 3, 4]
You can use this function:
function mapMap(map, fn) {
return new Map(Array.from(map, ([key, value]) => [key, fn(value, key, map)]));
}
usage:
var map1 = new Map([["A", 2], ["B", 3], ["C", 4]]);
var map2 = mapMap(map1, v => v * v);
console.log(map1, map2);
/*
Map { A → 2, B → 3, C → 4 }
Map { A → 4, B → 9, C → 16 }
*/
Using Array.from I wrote a Typescript function that maps the values:
function mapKeys<T, V, U>(m: Map<T, V>, fn: (this: void, v: V) => U): Map<T, U> {
function transformPair([k, v]: [T, V]): [T, U] {
return [k, fn(v)]
}
return new Map(Array.from(m.entries(), transformPair));
}
const m = new Map([[1, 2], [3, 4]]);
console.log(mapKeys(m, i => i + 1));
// Map { 1 => 3, 3 => 5 }
Actually you can still have a Map with the original keys after converting to array with Array.from. That's possible by returning an array, where the first item is the key, and the second is the transformed value.
const originalMap = new Map([
["thing1", 1], ["thing2", 2], ["thing3", 3]
]);
const arrayMap = Array.from(originalMap, ([key, value]) => {
return [key, value + 1]; // return an array
});
const alteredMap = new Map(arrayMap);
console.log(originalMap); // Map { 'thing1' => 1, 'thing2' => 2, 'thing3' => 3 }
console.log(alteredMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
If you don't return that key as the first array item, you loose your Map keys.
In typescript, in case somebody would need it :
export {}
declare global {
interface Map<K, V> {
map<T>(predicate: (key: K, value: V) => T): Map<V, T>
}
}
Map.prototype.map = function<K, V, T>(predicate: (value: V, key: K) => T): Map<K, T> {
let map: Map<K, T> = new Map()
this.forEach((value: V, key: K) => {
map.set(key, predicate(value, key))
})
return map
}
You can map() arrays, but there is no such operation for Maps. The solution from Dr. Axel Rauschmayer:
Convert the map into an array of [key,value] pairs.
Map or filter the array.
Convert the result back to a map.
Example:
let map0 = new Map([
[1, "a"],
[2, "b"],
[3, "c"]
]);
const map1 = new Map(
[...map0]
.map(([k, v]) => [k * 2, '_' + v])
);
resulted in
{2 => '_a', 4 => '_b', 6 => '_c'}
I prefer to extend the map
export class UtilMap extends Map {
constructor(...args) { super(args); }
public map(supplier) {
const mapped = new UtilMap();
this.forEach(((value, key) => mapped.set(key, supplier(value, key)) ));
return mapped;
};
}
You can use myMap.forEach, and in each loop, using map.set to change value.
myMap = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
for (var [key, value] of myMap.entries()) {
console.log(key + ' = ' + value);
}
myMap.forEach((value, key, map) => {
map.set(key, value+1)
})
for (var [key, value] of myMap.entries()) {
console.log(key + ' = ' + value);
}
const mapMap = (callback, map) => new Map(Array.from(map).map(callback))
var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);
var newMap = mapMap((pair) => [pair[0], pair[1] + 1], myMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }
If you don't want to convert the entire Map into an array beforehand, and/or destructure key-value arrays, you can use this silly function:
/**
* Map over an ES6 Map.
*
* #param {Map} map
* #param {Function} cb Callback. Receives two arguments: key, value.
* #returns {Array}
*/
function mapMap(map, cb) {
let out = new Array(map.size);
let i = 0;
map.forEach((val, key) => {
out[i++] = cb(key, val);
});
return out;
}
let map = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(
mapMap(map, (k, v) => `${k}-${v}`).join(', ')
); // a-1, b-2, c-3
Map.prototype.map = function(callback) {
const output = new Map()
this.forEach((element, key)=>{
output.set(key, callback(element, key))
})
return output
}
const myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]])
// no longer wishful thinking
const newMap = myMap.map((value, key) => value + 1)
console.info(myMap, newMap)
Depends on your religious fervor in avoiding editing prototypes, but, I find this lets me keep it intuitive.
Here is a Typescript variant which builds upon some of the existing ideas and attempts to be flexible in the following ways:
Both key and value types of output map can differ from input
The mapper function can access the input map itself, if it likes
function mapMap<TKI, TVI, TKO, TVO>(map: Map<TKI, TVI>,
f: (k: TKI, v: TVI, m: Map<TKI, TVI>) => [TKO, TVO]) : Map<TKO, TVO>{
return new Map([...map].map(p => f(p[0], p[1], map)))
}
Note: this code uses the spread operator, like some of the existing ideas, and so needs a target of of 'es2015' or higher according to my VS Code Intellisense.
Maybe this way:
const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map((k, v) => [k, v * 2]); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }
You would only need to monkey patch Map before:
Map.prototype.map = function(func){
return new Map(Array.from(this, ([k, v]) => func(k, v)));
}
We could have wrote a simpler form of this patch:
Map.prototype.map = function(func){
return new Map(Array.from(this, func));
}
But we would have forced us to then write m.map(([k, v]) => [k, v * 2]); which seems a bit more painful and ugly to me.
Mapping values only
We could also map values only, but I wouldn't advice going for that solution as it is too specific.
Nevertheless it can be done and we would have the following API:
const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map(v => v * 2); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }
Just like before patching this way:
Map.prototype.map = function(func){
return new Map(Array.from(this, ([k, v]) => [k, func(v)]));
}
Maybe you can have both, naming the second mapValues to make it clear that you are not actually mapping the object as it would probably be expected.

Categories

Resources