Assigning value 0 to unmatched item in two different array - javascript

The following code has two different array with a common field (key). I am using that key to combine these array and result a new array.
const listA = [
{"id": 1, "name":"Rohit"},
{"id": 2, "name":"Raj"},
{"id": 3, "name":"Maggie"}
]
const listB = [
{"id": 1, "count": 30},
{"id": 2, "count": 20}
]
const merge = listA.map(a => ({
...listB.find((b) => (b.id === a.id) && b), ...a
}))
console.log(merge)
How can I achieve 'count' : 0 for unmatched items from ListA ? I meant how can I achive following output:
[
{
"id": 1,
"count": 30,
"name": "Rohit"
},
{
"id": 2,
"count": 20,
"name": "Raj"
},
{
"id": 3,
"count": 0
"name": "Maggie",
}
]

Either you define count before the spread operator override (or not) the key.
const listA = [{
id: 1,
name: 'Rohit',
},
{
id: 2,
name: 'Raj',
},
{
id: 3,
name: 'Maggie',
},
];
const listB = [{
id: 1,
count: 30,
},
{
id: 2,
count: 20,
},
];
const merge = listA.map(a => ({
count: 0,
...listB.find(b => (b.id === a.id) && b),
...a,
}));
console.log(merge);
Either you handle the case where find doesn't find anything :
const listA = [{
id: 1,
name: 'Rohit',
},
{
id: 2,
name: 'Raj',
},
{
id: 3,
name: 'Maggie',
},
];
const listB = [{
id: 1,
count: 30,
},
{
id: 2,
count: 20,
},
];
const merge = listA.map(a => ({
...(listB.find(b => (b.id === a.id) && b) || {
count: 0,
}),
...a,
}));
console.log(merge);

You're almost there
if ...listB.find((b) => (b.id === a.id) && b) is undefined, you can use ...listB.find((b) => (b.id === a.id) && b) || { "count":0 } :
const listA = [
{"id": 1, "name":"Rohit"},
{"id": 2, "name":"Raj"},
{"id": 3, "name":"Maggie"}
]
const listB = [
{"id": 1, "count": 30},
{"id": 2, "count": 20}
]
const merge = listA.map(a => ({
...listB.find((b) => (b.id === a.id) && b) || { "count":0 }, ...a
}))
console.log(merge)

You can use below utility.
const listA = [
{"id": 1, "name":"Rohit"},
{"id": 2, "name":"Raj"},
{"id": 3, "name":"Maggie"}
]
const listB = [
{"id": 1, "count": 30},
{"id": 2, "count": 20}
]
const result = listA.map(objA => {
const foundObj = listB.find(objB => objB.id === objA.id) || {count: 0}
return {...objA, ...foundObj}
})
console.log(result)
Hope this helps

A possible solution would be
const listA = [
{"id": 1, "name":"Rohit"},
{"id": 2, "name":"Raj"},
{"id": 3, "name":"Maggie"}
]
const listB = [
{"id": 1, "count": 30},
{"id": 2, "count": 20}
]
const merge = listA.map(a => ({
...listB.find((b) => (b.id === a.id) && b) || {count: 0 }, ...a
}))
console.log(merge)

Just use a default value for count, if you don't find a matching item in listB;
const mapped = listA.map(({id, name}) => {
const {count} = {count: 0, ...listB.find(({id: _id}) => id === _id)}
return {id, name, count}
})
console.log (mapped);
<script>
const listA = [
{"id": 1, "name":"Rohit"},
{"id": 2, "name":"Raj"},
{"id": 3, "name":"Maggie"}
]
const listB = [
{"id": 1, "count": 30},
{"id": 2, "count": 20}
]
</script>

Related

Comparing Two Arrays with Array.filter and Pushing to New Arrays Based on Common Values

I am trying to use array.filter() to compare two arrays and separate out values that the two arrays have in common, based on a certain property (id), vs. values they don't have in common. The common ids I want to push to a new array (recordsToUpdate). And I want to push the remaining elements from arr2 to a new array (recordsToInsert).
What I've tried is not working. How can I rework this to get the results I wanted? - (which in the example here should be one array of 1 common element {id: 3}, and another array of the remaining elements from arr2):
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
let recordsToUpdate = [];
let recordsToInsert = [];
recordsToUpdate = arr1.filter(e => (arr1.id === arr2.id));
recordsToInsert = ?
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
The desired result should be:
recordsToUpdate = [{id: 3}];
recordsToInsert = [{id: 4}, {id: 5}];
Try this, which uses Array.prototype.find to test for whether an object exists in arr2 with a given id:
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
const recordsToUpdate = arr1.filter(e => arr2.find(obj => obj.id === e.id) !== undefined);
const recordsToInsert = arr1.filter(e => arr2.find(obj => obj.id === e.id) === undefined);
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
Update to Robin post using some instead of find. It is just other way around.
const arr1 = [{id: 1}, {id: 2}, {id: 3}];
const arr2 = [{id: 3}, {id: 4}, {id: 5}];
const recordsToUpdate = arr1.filter(e => arr2.some(obj => obj.id === e.id));
const recordsToInsert = arr2.filter(e => !arr1.some(obj => obj.id === e.id));
console.log('recordsToUpdate: ', recordsToUpdate);
console.log('recordsToInsert: ', recordsToInsert);
I think this is what you are after... I added values to show the replacement. If you are doing any kind of state management, be careful as I am directly mutating the current array.
const arr1 = [
{ id: 1, v: "a" },
{ id: 2, v: "b" },
{ id: 3, v: "old" }
];
const arr2 = [
{ id: 3, v: "new" },
{ id: 4, v: "e" },
{ id: 5, v: "f" }
];
function updateRecords(currentArray, updatesArray) {
const currentIds = currentArray.map(item => item.id);
updatesArray.forEach(updateItem =>
currentIds.includes(updateItem.id)
? (currentArray[
currentIds.findIndex(id => id === updateItem.id)
] = updateItem)
: currentArray.push(updateItem)
);
return currentArray;
}
console.log(updateRecords(arr1, arr2))
This now gives the option below:
[
{
"id": 1,
"v": "a"
},
{
"id": 2,
"v": "b"
},
{
"id": 3,
"v": "new"
},
{
"id": 4,
"v": "e"
},
{
"id": 5,
"v": "f"
}
]
Putting it in a function is also something you likely want to do as you will likely use this multiple places in your code.

finding index of item in nested array

If I have an array (sub) which has its own objects each with arrays within them and I'm looking for a particular value such as id === 9, how would I find the index of the object AND the index within that object's s array?
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id : 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.findIndex(a => a.s.findIndex(z => z.id === 9)))
If you're sure there's only one matching element in all your sub arrays, here's a little trick with flatMap.
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id: 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.flatMap((a, i) => {
const j = a.s.findIndex(z => z.id === 9);
return j > -1 ? [i, j] : []
}));
This will return an array containing the index, i, in a.sub where a matching element is found followed by the index, j, in a.sub[i].s where the matching element was found.
Note flatMap is a relatively recent addition to the standard, so it may not work in older browsers. Be sure to use a polyfill or a transpiler like Babel, if this is a concern in your case.
Try this:
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id : 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
v = 9
id1 = a.sub.findIndex(e => e.s.findIndex(ee => ee.id === v)!= -1)
id2 = a.sub[id1].s.findIndex(e => e.id === v )
console.log(id1) //index of the object
console.log(id2) //index within that object's s array
Modified answer of p.s.w.g, less likely to give you an eslint error.
let a = {
sub: [
{
id: 1,
s: [
{id: 5},
{id: 1}
]
},
{
id: 2,
s: [
{id: 6},
{id: 3}
]
},
{
id: 3,
s: [
{id: 9},
{id: 2}
]
}
]
}
console.log(a.sub.flatMap((a, i) => {
const j = a.s.findIndex(z => z['id'] === 9);
return j > -1 ? [i, j] : []
}));

How to merge two arrays and sum values of duplicate objects using lodash

There are two arrays:
[
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 1},
{"id": "344430b94t4t34rwefewfdff", "quantity": 5},
{"id": "342343343t4t34rwefewfd53", "quantity": 3}
]
and
[
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 2},
{"id": "344430b94t4t34rwefewfdff", "quantity": 1}
]
How to combine them into one summing quantity?
[
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 3},
{"id": "344430b94t4t34rwefewfdff", "quantity": 6},
{"id": "342343343t4t34rwefewfd53", "quantity": 3}
]
One of them can be empty sometimes
You can do it with plain JavaScript.
Use Array.reduce() to make an intermediate dictionary by id and accumulate the quantities, then turn it into an array with Object.values():
const arr1 = [
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 1},
{"id": "344430b94t4t34rwefewfdff", "quantity": 5},
{"id": "342343343t4t34rwefewfd53", "quantity": 3}
];
const arr2 = [
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 2},
{"id": "344430b94t4t34rwefewfdff", "quantity": 1}
];
const result = Object.values([...arr1, ...arr2].reduce((acc, { id, quantity }) => {
acc[id] = { id, quantity: (acc[id] ? acc[id].quantity : 0) + quantity };
return acc;
}, {}));
console.log(result);
You can use lodash but modern vanilla JS is just as viable and performant. I would imagine the other answers will be using functional methods like reduce, so here's a version that uses a simple for/of loop with find rather than a dictionary lookup which might be longer, but it is a little easier to follow.
const arr1 = [{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 1}, {"id": "344430b94t4t34rwefewfdff", "quantity": 5}, {"id": "342343343t4t34rwefewfd53", "quantity": 3}];
const arr2 = [{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 2}, {"id": "344430b94t4t34rwefewfdff", "quantity": 1}];
function merge(arr1, arr2) {
// Merge the arrays, and set up an output array.
const merged = [...arr1, ...arr2];
const out = [];
// Loop over the merged array
for (let obj of merged) {
// Destructure the object in the current iteration to get
// its id and quantity values
const { id, quantity } = obj;
// Find the object in out that has the same id
const found = out.find(obj => obj.id === id);
// If an object *is* found add this object's quantity to it...
if (found) {
found.quantity += quantity;
// ...otherwise push a copy of the object to out
} else {
out.push({ ...obj });
}
}
return out;
}
console.log(merge(arr1, arr2));
You can just do this with reduce:
let a1 = [
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 2},
{"id": "344430b94t4t34rwefewfdff", "quantity": 1}
];
let a2 = [
{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 1},
{"id": "344430b94t4t34rwefewfdff", "quantity": 5},
{"id": "342343343t4t34rwefewfd53", "quantity": 3}
];
let result = Object.values(a1.concat(a2).reduce((acc, v) => {
if (!acc[v.id]) {
acc[v.id] = {id: v.id, quantity: 0};
}
acc[v.id].quantity += v.quantity;
return acc;
}, {}));
console.log("Results: ", result);
You can use .reduce and .find methods to achieve this.
const arr1 = [{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 1}, {"id": "344430b94t4t34rwefewfdff", "quantity": 5}, {"id": "342343343t4t34rwefewfd53", "quantity": 3}];
const arr2 = [{"id": "5c5030b9a1ccb11fe8c321f4", "quantity": 2}, {"id": "344430b94t4t34rwefewfdff", "quantity": 1}];
const result = [...arr1, ...arr2].reduce((accumulator, currentValue) => {
const element = accumulator.find(item => item.id === currentValue.id)
element ? element.quantity += currentValue.quantity : accumulator.push(currentValue)
return accumulator
},[])
console.log(result)
All these answers require you to know the object structure to select and sum.
lodash does actually allow you to do this without knowing the structure; by using the customizer parameter of _.mergeWidth;
let result = _.mergeWith(arr1, arr2, (objValue, srcValue, key, object, source, stack) =>{
//Add any conditions you need here. Ive added a few relevant examples.
//if(key.startsWith("num")) //Check for property name prefixes like num...
//if(propertyNamesToSum.Includes(key)) //check if your property is in your predefined list of property names
//This one below sums any properties that are numbers
if(_.isNumber(srcValue) && _.isNumber(objValue)){
return srcValue + objValue;
}
return undefined; //lodash will merge as usual if you return undefined.
});
Lodash docs - https://lodash.com/docs/4.17.15#mergeWith
Version with additional object keys.
The body of the function does not interfere with what object has properties.
So sum by "qty" and check by "prop"
var first = [
{quantity:100, id:1, variantId: 1},
{quantity:300, id:2, variantId: 2, propA: 'aaa'},
];
var second = [
{quantity:100, id:1, variantId: 1},
{quantity:200, id:2, variantId: 2, propB: true},
{quantity:300, id:3, variantId: 3, propC: 'ccc'},
]
function mergeArrays(arrayOfArrays, propToCheck, propToSum) {
let sum = [];
[].concat(...arrayOfArrays).map(function(o) {
let existing = sum.filter(function(i) { return i[propToCheck] === o[propToCheck] })[0];
if (!existing) {
sum.push(o);
} else {
existing[propToSum] += o[propToSum];
let copyProps = Object.keys(o).filter(obj => {
return existing[obj] !== o[obj]
}).map(val => (val !== propToSum) ? existing[val] = o[val] : null)
}
});
return sum;
}
console.log(mergeArrays([first, second], 'variantId', 'quantity'))
This function uses lodash reduce and mapValues to sum the specified keys of an array of objects into a single result object. It allows missing keys in each object.
const mergeAndSumObjVals = (objs, keys) => _.reduce(
objs,
(o, s) => _.mapValues(o, (v, k) => (v || 0) + (s[k] || 0)),
_.chain(keys).invert().mapValues(() => 0).value(),
)
const objs = [{
negative: 54,
positive: 2
}, {
inconclusive: 8,
positive: 1
}, {
negative: 26,
inconclusive: 5,
positive: 4
}]
const result = mergeAndSumObjVals(objs, ['negative', 'inconclusive', 'positive'])
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>

How to use Lodash's unionBy and merge nested arrays?

Im looking to merge/combine objects in an array each with a series of nested arrays. I want to merge the objects based on a specific key (here label[1]). I can use Lodash and unionBy to filter out dublicates by label[1], but how do i keep the values from the filtered items?
The array can look like this:
var arr = [{
"label": ['item', 'private'],
"values": [1, 2, 3]
},
{
"label": ['item', 'private'],
"values": [1, 2, 3, 6]
},
{
"label": ['item', 'work'],
"values": [1, 2, 8, 9]
},
{
"label": ['item', 'private'],
"values": [1, 2, 4, 5]
},
{
"label": ['item', 'school'],
"values": [1, 2, 7]
}
];
And the desired output is:
var arr = [{
"label": ["item", "private"],
"values": [1, 2, 3, 4, 5, 6 ]
}, {
"label": ["item", "work"],
"values": [1, 2, 8, 9]
}, {
"label": ["item", "school"],
"values": [1, 2, 7]
}]
Here's a sample which is only half way there.
var arr = [
{ label: ['item','private'], values: [1,2,3] },
{ label: ['item','private'], values: [1,2,3,6] },
{ label: ['item','work'], values: [1,2,8,9] },
{ label: ['item','private'], values: [1,2,4,5] },
{ label: ['item','school'], values: [1,2,7] }
];
var result = _.unionBy(arr, "label[1]");
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Any ideas?
Thanks
Lasse
I'd write:
const arr2 = _(arr)
.groupBy(obj => obj.label[1])
.values()
.map(objs => ({
label: objs[0].label,
values: _(objs).flatMap("values").uniq().value(),
}))
.value()
Not sure how to do this with lodash but I don't think unionBy is the method to do this anyway.
Here is how you can group by label using lodash and then reduce the groups into one value to merge the items of a group.
const arr = [{"label":["item","private"],"values":[1,2,3]},{"label":["item","private"],"values":[1,2,3,6]},{"label":["item","work"],"values":[1,2,8,9]},{"label":["item","private"],"values":[1,2,4,5]},{"label":["item","school"],"values":[1,2,7]}];
console.log(
Object.values(
_.groupBy(arr, (item) => item.label.join()),//use lodash group by
).map((
group, //now we have array of array of groups
) =>
group
.reduce((result, item) => ({
//reduce a group to one object
label: result.label, //set label
values: [
//set values with unique values of all items
...new Set(
(result.values || []).concat(item.values || []),
),
],
})),
),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
You could also do something like this via lodash:
var arr = [{ "label": ['item', 'private'], "values": [1, 2, 3] }, { "label": ['item', 'private'], "values": [1, 2, 3, 6] }, { "label": ['item', 'work'], "values": [1, 2, 8, 9] }, { "label": ['item', 'private'], "values": [1, 2, 4, 5] }, { "label": ['item', 'school'], "values": [1, 2, 7] } ]
const merge = arr => _.reduce(arr, (r,c) => _.union(r, c.values), [])
const result = _(arr).groupBy('label')
.entries()
.reduce((r,[k,v]) => (r.push({ label: k.split(','), values: merge(v) }), r), [])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The idea is first to group by via _.groupBy and then get the entries (via _.entries) so you can form the desired output via _.reduce. _.union we use to merge the values arrays as part of the final reduce.
Here is the ES6 implementation:
var arr = [{ "label": ['item', 'private'], "values": [1, 2, 3] }, { "label": ['item', 'private'], "values": [1, 2, 3, 6] }, { "label": ['item', 'work'], "values": [1, 2, 8, 9] }, { "label": ['item', 'private'], "values": [1, 2, 4, 5] }, { "label": ['item', 'school'], "values": [1, 2, 7] } ]
const mrg = arr => Array.from(new Set(arr.reduce((r,c) => [...r, ...c.values], [])))
const grp = (arr, k) => arr.reduce((r,c) => (r[c[k]] = [...r[c[k]] || [], c], r), {})
const result = Object.entries(grp(arr, 'label'))
.reduce((r,[k,v]) => (r.push({ label: k.split(','), values: mrg(v) }), r), [])
console.log(result)

Reduce method - cannot get two seperate results

I've got an array like that:
const arr = [
[{rivals: ['player1','player2'], winner: "player1", player1Scored: 2, player2Scored: 1}],
[{rivals: ['player1','player3'], winner: "none", player1Scored: 2, player3Scored: 2}],
[{rivals: ['player2','player3'], winner: "player3", player2Scored: 1, player3Scored: 3}],
[{rivals: ['player1','player4'], winner: "none", player1Scored: 1, player4Scored: 1}]
]
I need to count scored points of every player, so it'll look like that:
{player1Scored: 5, player2Scored: 2, player3Scored: 5, player4Scored:1}
I tried this:
let scoreResult = arr.reduce((result, {0: obj}) => {
obj.rivals.forEach(rival => result[`${rival}Scored`] = result[`${rival}Scored`] || 0);
obj.rivals.forEach(rival => result[`${rival}Scored`] += obj.player1Scored)
return result;
}, {});
My mistake is that I'm asigning points of one player to two of them but cannot solve that.
Thank you for your help
To answer the problem, you have nested arrays with a single object. You could take a destructuring with the array and take the firts item as object.
Then take the names from rivals array and add the scores.
const
array = [[{ rivals: ['player1', 'player2'], winner: "player1", player1Scored: 2, player2Scored: 1 }], [{ rivals: ['player1', 'player3'], winner: "none", player1Scored: 2, player3Scored: 2 }], [{ rivals: ['player2', 'player3'], winner: "player3", player2Scored: 1, player3Scored: 3 }], [{ rivals: ['player1', 'player4'], winner: "none", player1Scored: 1, player4Scored: 1 }]],
result = array.reduce((r, [o]) => {
o.rivals.forEach(k => r[k + 'Scored'] = (r[k + 'Scored'] || 0) + o[k + 'Scored']);
return r;
}, Object.create(null));
console.log(result);
try this
scores = {}
arr.map(s => s[0]).forEach(s => {
s.rivals.forEach(r => {
scores[r] = scores[r] || 0;
scores[r] += s[`${r}Scored`]
})
})

Categories

Resources