Related
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>
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.
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>
I'm making a small tool for handling translations for a website. I already got this code working, but I feel there should be some more elegant and readable way using array methods (mine looks like a mess...).
Basically, I'll get input in the format shown in code (data_import, this is just fake data for testing). It has 4 columns [translationTag, uniqueId, languageId, translation]. Order of rows is same for every language and there is same number of rows for each language. Number of languages may change from 2 upwards.
the desired output would be like this:
const data_import = [
['aaa', {id:1, langId:1, finnish:'tuntematon'}, {id:5, langId:4, english:'unknown'}, {id:9, langId:6, swedish:'okänd'}],
['bbb', {id:2, langId:1, finnish:'auto'}, {id:6, langId:4, english:'car'}, {id:10, langId:6, swedish:'bil'}],
['ccc', {id:3, langId:1, finnish:'polkupyörä'}, {id:7, langId:4, english:'bicycle'}, {id:11, langId:6, swedish:'cykel'}],
['ddd', {id:4, langId:1, finnish:'rullalauta'}, , {id:8, langId:4, english:'skateboard'}, {id:12, langId:6, swedish:'skateboard'}]
];
Here is my code that 'works' but is ugly and unreadable...
export const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [
['aaa', 1, 1, 'tuntematon'],
['bbb', 2, 1, 'auto'],
['ccc', 3, 1, 'polkupyörä'],
['ddd', 4, 1, 'rullalauta'],
['aaa', 5, 4, 'unknown'],
['bbb', 6, 4, 'car'],
['ccc', 7, 4, 'bicycle'],
['ddd', 8, 4, 'skateboard'],
['aaa', 9, 6, 'okänd'],
['bbb', 10, 6, 'bil'],
['ccc', 11, 6, 'cykel'],
['ddd', 12, 6, 'skateboard']];
export const data = process_test(data_import);
function process_test(data) {
const numberOfCols = data[0].length;
const idIndex = numberOfCols - 2;
const arr_result = []
let rowMax = 0;
let rowMaxMulti = 0;
let langIdLast = 0;
data.forEach((row, index) => {
// if = add non-language cols and first language column
if(row[idIndex] === data[0][idIndex]) {
rowMax = index + 1;
const transItem = row.slice(0, idIndex-1);
transItem.push({ id:row[idIndex], langId:row[idIndex], [language[row[idIndex] - 1]]:row[idIndex + 1] });
arr_result[index] = transItem;
langIdLast = row[idIndex];
}
// add other languages to datarow
else {
const transItem = { id:row[idIndex - 1], langId:row[idIndex], [language[row[idIndex] + 1]]:row[idIndex + 1] };
if(langIdLast !== row[idIndex]) rowMaxMulti++;
arr_result[index - rowMax * rowMaxMulti].push(transItem);
langIdLast = row[idIndex];
}
})
return(arr_result);
}
It would be simpler to reduce into an object indexed by the translation tag, and then get that object's values. On each iteration, create an array for the translation tag if it doesn't already exist in the accumulator. Identify the language name from the langId of the current item, and push the new object to the array:
const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [
['aaa', 1, 1, 'tuntematon'],
['bbb', 2, 1, 'auto'],
['ccc', 3, 1, 'polkupyörä'],
['ddd', 4, 1, 'rullalauta'],
['aaa', 5, 4, 'unknown'],
['bbb', 6, 4, 'car'],
['ccc', 7, 4, 'bicycle'],
['ddd', 8, 4, 'skateboard'],
['aaa', 9, 6, 'okänd'],
['bbb', 10, 6, 'bil'],
['ccc', 11, 6, 'cykel'],
['ddd', 12, 6, 'skateboard']];
const data = Object.values(data_import.reduce((a, [tTag, id, langId, word]) => {
if (!a[tTag]) a[tTag] = [tTag];
const langName = language[langId - 1];
a[tTag].push({ id, langId, [langName]: word });
return a;
}, {}));
console.log(data);
You can group the data by tag with Array.prototype.reduce and map it out to the desired format with Object.keys
const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [['aaa', 1, 1, 'tuntematon'],['bbb', 2, 1, 'auto'],['ccc', 3, 1, 'polkupyörä'],['ddd', 4, 1, 'rullalauta'],['aaa', 5, 4, 'unknown'],['bbb', 6, 4, 'car'],['ccc', 7, 4, 'bicycle'],['ddd', 8, 4, 'skateboard'],['aaa', 9, 6, 'okänd'],['bbb', 10, 6, 'bil'],['ccc', 11, 6, 'cykel'],['ddd', 12, 6, 'skateboard']];
const grouped = data_import.reduce((all, [tag, id, langId, tran]) => {
if (!all.hasOwnProperty(tag)) all[tag] = [];
all[tag].push({id, langId, [language[langId-1]]: tran});
return all;
}, {});
const result = Object.keys(grouped).map(tag => [tag, ...grouped[tag]]);
console.log(result);
You could also use
Array.prototype.map()
Array.prototype.filter()
new Set([])
destructuring assignment
const data_import = [["aaa",1,1,"tuntematon"],["aaa",5,4,"unknown"],["aaa",9,6,"okänd"],["bbb",6,4,"car"],["bbb",2,1,"auto"],["bbb",10,6,"bil"],["ccc",11,6,"cykel"],["ccc",7,4,"bicycle"],["ccc",3,1,"polkupyörä"],["ddd",8,4,"skateboard"],["ddd",4,1,"rullalauta"],["ddd",12,6,"skateboard"]],
language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const keys =[...new Set(data_import.map(v => v[0]))];
let result = keys.map(key => [key, data_import.filter(v => v[0] == key).map(v => {
return {
id: v[1],
langId: v[2],
[language[v[2]-1]]: v[3]
}
})]);
console.log(result)
.as-console-wrapper {max-height: 100% !important;top: 0;}
I have two arrays of objects like this:
var arr1 = [{Id: 1, Name: "Test1"}, {Id: 2, Name: "Test2"}, {Id: 3, Name: "Test3"}, {Id: 4, Name: "Test4"}]
var arr2 = [{Id: 1, Name: "Test1"}, {Id: 3, Name: "Test3"}]
I need to compare the elements of the two arrays by Id and remove the elements from arr1 that are not presented in arr2 ( does not have element with that Id). How can I do this ?
var res = arr1.filter(function(o) {
return arr2.some(function(o2) {
return o.Id === o2.Id;
})
});
shim, shim, shim.
You can use a function that accepts any number of arrays, and returns only the items that are present in all of them.
function compare() {
let arr = [...arguments];
return arr.shift().filter( y =>
arr.every( x => x.some( j => j.Id === y.Id) )
)
}
var arr1 = [{Id: 1, Name: "Test1"}, {Id: 2, Name: "Test2"}, {Id: 3, Name: "Test3"}, {Id: 4, Name: "Test4"}];
var arr2 = [{Id: 1, Name: "Test1"}, {Id: 3, Name: "Test3"}, {Id: 30, Name: "Test3"}];
var arr3 = [{Id: 1, Name: "Test1"}, {Id: 6, Name: "Test3"}, {Id: 30, Name: "Test3"}];
var new_arr = compare(arr1, arr2, arr3);
console.log(new_arr);
function compare() {
let arr = [...arguments]
return arr.shift().filter( y =>
arr.every( x => x.some( j => j.Id === y.Id) )
)
}
Making use of a hash (a Set) will give a performance gain:
var arr1 = [{Id: 1, Name: "Test1"}, {Id: 2, Name: "Test2"},
{Id: 3, Name: "Test3"}, {Id: 4, Name: "Test4"}];
var arr2 = [{Id: 1, Name: "Test1"}, {Id: 3, Name: "Test3"}];
arr1 = arr1.filter(function (el) {
return this.has(el.Id);
}, new Set(arr2.map(el => el.Id)));
console.log(arr1);
A new Set is created that gets the Id values from arr2:
"1","3"
That Set is passed as the thisArg to filter, so that within the filter callback it is available as this.