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.
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)
Hell,
I'm working for implementation for R.partition from 'ramda'. without using push!!
only using map, filter, reduce functions.
This is my current implementation :
partition = <T>(func : (param : T) => boolean, arr : T[]): T[][] => {
return [arr.filter((element : T) => func(element)), arr.filter((element : T) => !func(element))];
}
example :
const func = (x) => x%2 == 0
const temp = [1,2,3,4,5,6,7,8,9];
console.log(partition(func, tmep)); // => [[2,4,6,8],[1,3,5,7,9]
A double filtering introduces unnecessary time complexity,
we don't really need to iterate twice here.
Array#reduce is our tool here:
const partition = (predicate, list) => list.reduce(
([left, right], item) => predicate(item)
? [left.concat(item), right]
: [left, right.concat(item)],
[[], []],
list
);
//===
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const isOdd = n => n % 2;
const [odds, evens] = partition(isOdd, data);
console.log({ odds, evens });
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)
I have an object which looks like this:
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
}
I want to loop over this object and this and log out each key name e.g. eyes for the amount of the value.
this would result in:
head
eyes
eyes
arms
arms
legs
legs
legs
Currently I have this solution but it feels like it could be done more neatly and readible.
Object.keys(object)
.map(key => {
return [...Array(object[key])].map( (_, i) => {
return console.log(key)
})
Any suggestions?
You could use Object.entries() and map() method and return new array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = [].concat(...Object.entries(object).map(([k, v]) => Array(v).fill(k)))
console.log(res)
Or you could use reduce() with spread syntax in array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = Object
.entries(object)
.reduce((r, [k, v]) => [...r, ...Array(v).fill(k)], [])
// Or you can use push instead
// .reduce((r, [k, v]) => (r.push(...Array(v).fill(k)), r), [])
console.log(res)
Object.entries(object)
.forEach(([key, times]) => console.log((key + "\n").repeat(times)));
One may use String.prototype.repeat...
"...it feels like it could be done more neatly and readible."
Recursion makes it pretty clean and understandable.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
if (v) {
console.log(k);
f([k, --v]);
}
})
You can rearrange things a bit if you know the value will always be greater than 0.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
console.log(k);
if (--v) f([k, v]);
})