Conditional spread element - javascript

const cond = false
const extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
]
const userInfo = [
{
z: 8
},
{
z: 10
},
...(cond && extraInfo)
]
When cond is true, I want both extra and user info.
When cond is false, only userInfo is needed.
The issue is when cond is false, I get
TypeError: (intermediate value)(intermediate value)(intermediate value)[Symbol.iterator] is not a function
My understanding is that I am not allowed to use a boolean as a spread element, in this case ...false.
But ...( cond ? extraInfo : {} ) doesn't seem to work either.
What is going on?

Just make it
...(cond ? extraInfo : [])
Demo with true
var cond = true;
var extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
];
var userInfo = [
{
z: 8
},
{
z: 10
},
...(cond ? extraInfo : [])
];
console.log( userInfo );
Demo with false
var cond = false;
var extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
];
var userInfo = [
{
z: 8
},
{
z: 10
},
...(cond ? extraInfo : [])
];
console.log( userInfo );

Conditionally spread an entity to Object
console.log(
{
name: 'Alex',
age: 19,
...(true && { city: 'Kyiv' }),
...(false && { country: 'Ukraine' })
}
)
// { name: 'Alex', age: 19, city: 'Kyiv' }
Conditionally spread an entity to Array
console.log(
[
'Dan',
'Alex',
...(true ? ['Robin'] : [])
]
)
// [ 'Dan', 'Alex', 'Robin' ]

const extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
];
const userInfo = [
{
z: 8
},
{
z: 10
},
];
const cond = true;
let getMyValue = cond ? [].concat(extraInfo, userInfo) : userInfo;
console.log(getMyValue)

Another way:
cond is true:
var extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
]
var cond = true;
var userInfo = [
{
z: 8
},
{
z: 10
},
...(cond && extraInfo || [])
]
console.log(userInfo);
cond is false:
var extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
]
var cond = false;
var userInfo = [
{
z: 8
},
{
z: 10
},
...(cond && extraInfo || [])
]
console.log(userInfo);

let getMyValue = cond ? [].concat(extraInfo, userInfo) : userInfo;
let check this
const extraInfo = [
{
a: 11,
b: 25
},
{
a: 12,
b: 34
},
{
a: 1,
c: 99
}
];
const userInfo = [
{
z: 8
},
{
z: 10
},
];
const cond = false;
let getMyValue = cond ? [].concat(extraInfo, userInfo) : userInfo;
console.log(getMyValue)

Related

How to elegantly merge multiple objects with overlapping keys?

Let's consider multiple objects with overlapping keys, where each key indicates a week of the year and the values are objects of integer test results, like
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } };
const cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } };
const xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } };
What is an elegant way to merge them to a single object that contains all weeks as keys and the values as merged-objects, such that:
const merged_tests = {
week1: { a: 4, b: 6, x: 1, y: 1 },
week2: { a: 0, b: 9, c: 2, d: 5 },
week3: { c: 6, d: 7 },
week4: { x: 100, y: 123 },
};
Using Array#reduce, iterate over the objects while updating the final one (accumulator)
In each iteration, using Object#entries and Array#forEach, iterate over the pairs of the current object and update the final one
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } };
const cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } };
const xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } };
const merged = [ab_tests, cd_tests, xy_tests].reduce((merged, current) => {
Object.entries(current).forEach(([key, value]) => {
merged[key] ??= {};
merged[key] = { ...merged[key], ...value };
});
return merged;
}, {});
console.log(merged);
You could loop through the key of each object and update an output object with the same key
const inputs = [ab_tests, cd_tests, xy_tests],
output = { }
for (const o of inputs) {
for (const key in o)
Object.assign(output[key] ??= {}, o[key])
}
Here's a snippet:
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } },
cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } },
xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } },
inputs = [ab_tests, cd_tests, xy_tests],
output = {}
for (const o of inputs) {
for (const key in o)
Object.assign(output[key] ??= {}, o[key])
}
console.log(output)
flatMap(Object.entries) flattens things so there's only one loop to read, making it a little more readable in my opinion.
function merge(...tests) {
return tests.flatMap(Object.entries).reduce(
(obj, [k, v]) => Object.assign(obj, {[k]: {...obj[k], ...v}}), {});
}
console.log(merge(ab_tests, cd_tests, xy_tests));
You could reduce the array of objects by grouping with the keys of the outer objects.
const
merge = array => array.reduce((r, o) => Object
.entries(o)
.reduce((t, [k, q]) => {
Object.assign(t[k] ??= {}, q);
return t;
}, r),
{}),
ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } },
cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } },
xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } },
result = merge([ab_tests, cd_tests, xy_tests]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to loop and push on the accumulator of a reduce function?

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);

how to bundle objects in an array by date attribute

I have a big array with data. Here is a example of the structure:
let data = [
{
date: '2018-11-22',
values: {
a: 10,
b: 20,
c: 5,
},
},
{
date: '2018-11-17',
values: {
a: 5,
b: 10,
c: 15,
},
},
{
date: '2018-06-29',
values: {
a: 10,
b: 30,
c: 10,
},
},
{
date: '2017-12-20',
values: {
a: 30,
b: 40,
c: 5,
},
},
];
I need this data structured in a new array by month and year. The value attributes should be summed up for each month.
So the new array for the example should look like this:
let sortedData = [
{
date: '2018-11',
values: {
a: 15,
b: 30,
c: 20,
},
},
{
date: '2018-06',
values: {
a: 10,
b: 30,
c: 10,
},
},
{
date: '2017-12',
values: {
a: 30,
b: 40,
c: 5,
},
},
];
I'm trying for hours to write a working function but I can't handle it.
Any ideas how I can bundle an array like this?
Thanks for your help!
You can use Array.reduce for this
let data = [ { date: '2018-11-22', values: { a: 10, b: 20, c: 5, }, }, { date: '2018-11-17', values: { a: 5, b: 10, c: 15, }, }, { date: '2018-06-29', values: { a: 10, b: 30, c: 10, }, }, { date: '2017-12-20', values: { a: 30, b: 40, c: 5, }, },];
let res = data.reduce((o, {date, values}) => {
let k = date.slice(0, 7)
o[k] = o[k] || {date: k, values: {a: 0, b: 0, c:0}}
o[k].values.a += values.a
o[k].values.b += values.b
o[k].values.c += values.c
return o
}, {})
console.log(Object.values(res))
You can also do make it more concise and not deal with the individual values props like this:
let data = [{ date: '2018-11-22', values: { a: 10, b: 20, c: 5, }, }, { date: '2018-11-17', values: { a: 5, b: 10, c: 15, }, }, { date: '2018-06-29', values: { a: 10, b: 30, c: 10, }, }, { date: '2017-12-20', values: { a: 30, b: 40, c: 5, }, }, ];
const result = data.reduce((r, {date, values}) => {
date = date.substr(0,7)
r[date] = r[date]
? (Object.keys(values).forEach(k => r[date].values[k] += values[k]), r[date])
: {date, values}
return r
}, {})
console.log(Object.values(result))
This way you would not care if there are 3 of 10 properties in values and you get more generic solution.

sheetjs xlsx, How to write merged cells?

I need to create a xlsx with merged cells using sheetjs.
data:
[
{
"id": "nick",
"items": [
{
"name": "ball"
},
{
"name": "phone"
}
]
},
{
"id": "jack",
"items": [
{
"name": "pen"
},
{
"name": "doll"
}
]
}
]
My code:
var ws = XLSX.utils.json_to_sheet(data);
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "");
var wbout = XLSX.write(wb, {bookType:'xlsx', type:'array'});
saveAs(new Blob([wbout],{type:"application/octet-stream"}), filename + ".xlsx");
The result I want to get:
How do I get this result?
... Thank you
const merge = [
{ s: { r: 1, c: 0 }, e: { r: 2, c: 0 } },{ s: { r: 3, c: 0 }, e: { r: 4, c: 0 } },
];
ws["!merges"] = merge;
Use this code for merge A2:A3 ({ s: { r: 1, c: 0 }, e: { r: 2, c: 0 } })
and A4:A5 ({ s: { r: 3, c: 0 }, e: { r: 4, c: 0 } })
Here s = start, r = row, c=col, e= end

How can I sum properties from two objects?

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)

Categories

Resources