Related
I'm trying to reduce an array, and transform it in multiple array.
const array = [
{ a: 1, b: 6 },
{ a: 1, b: 5 },
{ a: 1, b: 6 },
{ a: 1, b: 4 },
{ a: 1, b: 5 }
];
var newArray = array.reduce(
(memo, curr) => {
memo.forEach((item, key) => {
const found = item.filter((el) => el.a === curr.a && el.b === curr.b);
if (found.length > 0) return memo[key].push(curr);
else return memo.push([curr]);
});
return memo;
},
[[]]
);
The needed result I try to get is
[
[
{ a: 1, b: 5 },
{ a: 1, b: 5 }
],
[
{ a: 1, b: 6 },
{ a: 1, b: 6 },
],
[
{ a: 1, b: 4 },
]
];
But as you can see if you try, because I push on the memo, the loop continue to fire. And the result contain hundreds arrays.
How I'm supposed to do to limit this loop and get the right result ?
Thanks a lot in advance :)
You could use Map to group the element by the key of {a, b}, and then get the values of the group
const array = [
{ a: 1, b: 6 },
{ a: 1, b: 5 },
{ a: 1, b: 6 },
{ a: 1, b: 4 },
{ a: 1, b: 5 },
];
var newArray = Array.from(
array
.reduce((map, curr) => {
const key = JSON.stringify({ a: curr.a, b: curr.b });
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(curr);
return map;
}, new Map())
.values()
);
console.log(newArray);
Look at your code. You have a triple nested loop, which is insane and definitely not needed to achieve this. Why not use a map?
Here is a function that will do what you want to do with any array of objects given.
const array = [
{ a: 1, b: 6 },
{ a: 1, b: 5 },
{ a: 1, b: 6 },
{ a: 1, b: 4 },
{ a: 1, b: 5 },
];
const separate = (arr) => {
const reduced = arr.reduce((acc, curr) => {
const path = JSON.stringify(curr);
if (!acc[path]) acc[path] = [];
acc[path].push(curr);
return acc;
}, {});
return Object.values(reduced);
};
console.log(separate(array));
If you push inside for loop it will going to push for every reduce function iteration also.
you can achieve by adding some local variables like here
const array = [
{ a: 1, b: 6 },
{ a: 1, b: 5 },
{ a: 1, b: 6 },
{ a: 1, b: 4 },
{ a: 1, b: 5 }
];
// shift changes the orginal array
// it will remove and return firstElement
var firstElement = array.shift(1);
var newArray = array.reduce(
(memo, curr) => {
let isFound = false;
let index = 0;
memo.forEach((item, key) => {
const found = item.filter((el) => el.a === curr.a && el.b === curr.b);
if(found.length > 0){
index = key;
isFound = true;
return;
}
});
if(isFound) {
memo[index].push(curr);
} else {
memo.push([curr]);
}
return memo;
},
[[firstElement]]
);
console.log(newArray);
So I have an array of objects with some varying number of properties (but the property names are known), for example:
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
I need to construct an object that sums up the values in the respective properties, so in this example I'd need to construct an object {a: 50, b: 6, c:67}
I wrote the following function to do this:
calcTotalForDataProps(data, props) {
let summedData = {}
for (const prop of props) {
summedData[prop] = 0;
}
data.forEach((dataObj) => {
for (const prop of props) {
summedData[prop] += dataObj[prop];
}
});
return summedData;
}
And you call it like this:
calcTotalForDataProps(data, ['a', 'b', 'c']);
But I'm wondering if there's a much shorter way to write this with ES6?
You could map the wanted props with their new sums for getting an object as result.
function calcTotalForDataProps(data, props) {
return data.reduce((r, o) => Object
.fromEntries(props.map(k => [k, (r[k] || 0) + o[k]])
), {});
}
const data = [{ a: 10, b: 1, c: 10}, { a: 17, b: 2, c: 16 }, { a: 23, b: 3, c: 41 }]
console.log(calcTotalForDataProps(data, ['a', 'b', 'c']));
There's no need to iterate over the properties initially - you can create the property inside the other loop if it doesn't exist yet.
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
const calcTotalForDataProps = (data, props) => {
const summedData = {};
for (const obj of data) {
for (const [prop, num] of Object.entries(obj)) {
summedData[prop] = (summedData[prop] || 0) + num;
}
}
return summedData;
}
console.log(calcTotalForDataProps(data));
im trying to add counting number for duplicate in JS.
and i am completely stack in this case below.
i need to compare objects with two value (x, y) and if there are same values of (x, y) add count 1 on new objects.
is there any way to convert data to newData such as below?
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const newData = [
{x: 1, y:1 ,count:2}
{x: 2, y:2 ,count:1}
]
use .reduce() function
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const output = data.reduce((acc, curr) => {
curr.count = 1;
const exists = acc.find(o => o.x === curr.x && o.y === curr.y);
exists ? exists.count++ : acc.push(({ x, y, count } = curr));
return acc;
}, []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way of doing so, is to create a map with the x and y values, and increment the count accordingly, then convert the map into an array:
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const makeXYMap = (data) => data.reduce((acc, cur) => {
const { x, y } = cur;
const entry = acc[`${x}_${y}`];
if (entry) {
acc[`${x}_${y}`] = {...entry, count: entry.count + 1};
} else {
acc[`${x}_${y}`] = { x, y, count: 1 };
}
return acc;
}, {});
const makeArray = (XYMap) => Object.values(XYMap);
console.log(makeArray(makeXYMap(data)));
Note that complexity wise, this solution is a O(N).
https://jsfiddle.net/9o35neg7/
const data = [
{ id: 1, x: 1, y: 1 },
{ id: 2, x: 2, y: 2 },
{ id: 3, x: 1, y: 1 },
// .. so on ..
];
const countedData = data.reduce((acc, { x, y }, index, array) => {
acc[`x${x}y${y}`] = {
x,
y,
count: (acc[`x${x}y${y}`] ? acc[`x${x}y${y}`].count : 0) + 1
};
return index === (array.length - 1) ? Object.values(acc) : acc;
}, {});
console.log(countedData);
Use forEach and build an object with key (made of x, y) and values (aggregate count). Get the Object.values to get the results as array.
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const counts = (arr, res = {}) => {
arr.forEach(({x , y}) =>
res[`${x}-${y}`] = { x, y, count: (res[`${x}-${y}`]?.count ?? 0) + 1 })
return Object.values(res);
}
console.log(counts(data))
I have multiple JavaScript objects:
{
a: 12,
b: 8,
c: 17
}
and
{
a: 2,
b: 4,
c: 1
}
I need to sum these two object by keys
Result:
{
a: 14,
b: 12,
c: 18
}
Do you have any solutions in JavaScript?
I use Object.keys.map but it's too long because I have like 100 elements in my object.
You can use reduce for that, below function takes as many objects as you want and sums them by key:
var obj1 = {
a: 12,
b: 8,
c: 17
};
var obj2 = {
a: 12,
b: 8,
c: 17
};
var obj3 = {
a: 12,
b: 8,
c: 17
};
function sumObjectsByKey(...objs) {
return objs.reduce((a, b) => {
for (let k in b) {
if (b.hasOwnProperty(k))
a[k] = (a[k] || 0) + b[k];
}
return a;
}, {});
}
console.log(sumObjectsByKey(obj1, obj2, obj3));
A little bit deeper, all you want as long as objects are equivalent!
const arr = [{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
},
{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
},
{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
}
];
const deepMergeSum = (obj1, obj2) => {
return Object.keys(obj1).reduce((acc, key) => {
if (typeof obj2[key] === 'object') {
acc[key] = deepMergeSum(obj1[key], obj2[key]);
} else if (obj2.hasOwnProperty(key) && !isNaN(parseFloat(obj2[key]))) {
acc[key] = obj1[key] + obj2[key]
}
return acc;
}, {});
};
const result = arr.reduce((acc, obj) => acc = deepMergeSum(acc, obj));
console.log('result: ', result);
Try this.
let t1 =
{
a:12,
b:8,
c:17
};
let t2 =
{
a:2,
b:4,
c:1
};
function sum(ob1, ob2) {
let sum = {};
Object.keys(ob1).forEach(key => {
if (ob2.hasOwnProperty(key)) {
sum[key] = ob1[key] + ob2[key]
}
})
return sum;
}
sum(t1, t2);
https://jsfiddle.net/fbnt2vhe/
If the objects have all common keys, you could take the keys from one object in advance and iterate for creating a new result object and later the keys from the single objects.
var o1 = { a: 12, b: 8, c: 17 },
o2 = { a: 2, b: 4, c: 1 },
keys = Object.keys(o1),
result = [o1, o2].reduce(function (r, o) {
keys.forEach(function (k) {
r[k] += o[k];
});
return r;
}, keys.reduce(function (r, k) {
r[k] = 0;
return r;
}, Object.create(null)));
console.log(result);
If you have just two objects:
const x = { a: 12, b: 8, c: 17 }
const y = { a: 2, b: 4, c: 1 }
const z = Object.fromEntries(Object.keys(x).map(k=>[k,x[k]+y[k]]))
console.log(z)
or, if you have many objects:
const arr = [{ a: 33, b: 44, c: 55 }, { a: 12, b: 8, c: 17 }, { a: 2, b: 4, c: 1 }]
const z = Object.fromEntries(Object.keys(arr[0]).map(k=>[k,arr.reduce((s,o)=>s+o[k],0)]))
console.log(z)
Node.js app, writing validation tests. Given the following:
var obj = { foo: null, bar: null, baz: null},
values = [ 0, 1];
I need to create n number of objects to account for every property being assigned every combination of possible values, to represent every possible use case. So for this example, the output should be 2^3=8 objects, e.g.
[
{ foo: 0, bar: 0, baz: 0},
{ foo: 0, bar: 1, baz: 0},
{ foo: 0, bar: 1, baz: 1},
{ foo: 0, bar: 0, baz: 1},
{ foo: 1, bar: 0, baz: 0},
{ foo: 1, bar: 1, baz: 0},
{ foo: 1, bar: 1, baz: 1},
{ foo: 1, bar: 0, baz: 1},
]
Underscore or lodash or other libraries are acceptable solutions. Ideally, I would like something like so:
var mapUseCases = function(current, remaining) {
// using Underscore, for example, pull the current case out of the
// possible cases, perform logic, then continue iterating through
// remaining cases
var result = current.map(function(item) {
// perform some kind of logic, idk
return magic(item);
});
return mapUseCases(result, _.without(remaining, current));
}
var myValidationHeadache = mapUseCases(currentThing, somethingElse);
Pardon my pseudocode, I think I broke my brain. ¯\_(ツ)_/¯
Solution for any object length and any values.
Please note, undefined values do not show up.
function buildObjects(o) {
var keys = Object.keys(o),
result = [];
function x(p, tupel) {
o[keys[p]].forEach(function (a) {
if (p + 1 < keys.length) {
x(p + 1, tupel.concat(a));
} else {
result.push(tupel.concat(a).reduce(function (r, b, i) {
r[keys[i]] = b;
return r;
}, {}));
}
});
}
x(0, []);
return result;
}
document.write('<pre>' + JSON.stringify(buildObjects({
foo: [0, 1, 2],
bar: [true, false],
baz: [true, false, 0, 1, 42]
}), 0, 4) + '</pre>');
One way is to count from "000" to "999" in a values.length-based system:
keys = ['foo','bar','baz']
values = ['A', 'B']
width = keys.length
base = values.length
out = []
for(var i = 0; i < Math.pow(base, width); i++) {
var d = [], j = i;
while(d.length < width) {
d.unshift(j % base)
j = Math.floor(j / base)
}
var p = {};
for(var k = 0; k < width; k++)
p[keys[k]] = values[d[k]]
out.push(p)
}
document.write('<pre>'+JSON.stringify(out,0,3))
Update for products:
'use strict';
let
keys = ['foo', 'bar', 'baz'],
values = [
['A', 'B'],
['a', 'b', 'c'],
[0, 1]
];
let zip = (h, t) =>
h.reduce((res, x) =>
res.concat(t.map(y => [x].concat(y)))
, []);
let product = arrays => arrays.length
? zip(arrays[0], product(arrays.slice(1)))
: [[]];
let combine = (keys, values) =>
keys.reduce((res, k, i) =>
(res[k] = values[i], res)
, {});
let z = product(values).map(v => combine(keys, v));
z.map(x => document.write('<pre>'+JSON.stringify(x)+'</pre>'))
This is a non-recursive version of what you want:
function createRange(keys, values) {
if (typeof values[0] !== typeof [])
values = keys.map(k => values);
var pointer = {};
var repeats = 1;
keys.forEach((k, i) => {
var vLen = values[i].length;
repeats *= vLen;
pointer[k] = {
get value() {
return values[i][pointer[k].current]
},
current: 0,
period: Math.pow(vLen, i),
inc: function() {
var ptr = pointer[k];
ptr.current++;
if (ptr.current < vLen) return;
ptr.current = 0;
if (i + 1 === keys.length) return;
var nk = keys[i + 1];
pointer[nk].inc()
}
};
});
var result = [];
for (var i = 0; i < repeats; i++) {
var o = {};
result.push(o);
keys.forEach(k => o[k] = pointer[k].value)
pointer[keys[0]].inc();
}
return result;
}
var objKeys = ['u', 'v', 'w', 'x', 'y', 'z'];
var objValues = [
['1', '2', '3'],
['a', 'b', 'c'],
['foo', 'bar', 'baz'],
[1, 3, 2],
['test', 'try', 'catch'],
['Hello', 'World'],
];
var range = createRange(objKeys, objValues);
range.map(v => document.write(JSON.stringify(v).big()))