combine array of objects by key - javascript

I am trying to combine/merge 2 array of objects by key in my case id.
Objective:
I am expecting a results where I would have array containing all objects with ids 1,2,3,4 as per example
Order of merging should not affect number of objects in result for example combine(arr1,arr2) or combine(arr2,arr1) should have array with same number of objects
Order of merging can only affect resulting object for example in case of combine(arr1,arr2) arr2 key,values pair can override arr1 key,values just like deep jquery extend $.extend( true, arr1ObJ,arr2ObJ );
JSFIDDLE: https://jsfiddle.net/bababalcksheep/u2c05nyj/
Sample Data:
var arr1 = [{
id: 1,
name: "fred",
title: "boss"
}, {
id: 2,
name: "jim",
title: "nobody"
}, {
id: 3,
name: "bob",
title: "dancer"
}];
var arr2 = [{
id: 1,
wage: "300",
rate: "day"
}, {
id: 2,
wage: "10",
rate: "hour"
}, {
id: 4,
wage: "500",
rate: "week"
}];
var Result = [{
"id": 1,
"name": "fred",
"title": "boss",
"wage": "300",
"rate": "day"
}, {
"id": 2,
"name": "jim",
"title": "nobody",
"wage": "10",
"rate": "hour"
}, {
id: 3,
name: "bob",
title: "dancer"
}, {
id: 4,
wage: "500",
rate: "week"
}];

Here's a solution. It basically goes through each element of arr2 and checks to see if there's an element with a matching ID arr1. If so, it updates the matching element in arr1 with arr2's values. If there is no match, it simply pushes the element in arr2 onto arr1.
var arr1 = [{id: 1,name: 'fred',title: 'boss'},
{id: 2,name: 'jim',title: 'nobody'},
{id: 3,name: 'bob',title: 'dancer'}];
var arr2 = [{id: 1,wage: '300',rate: 'day'},
{id: 2,wage: '10',rate:'hour'},
{id: 4,wage: '500',rate: 'week'}];
function combineArrays(arr1, arr2) {
for(var i = 0; i < arr2.length; i++) {
// check if current object exists in arr1
var idIndex = hasID(arr2[i]['id'], arr1);
if(idIndex >= 0){
//update
for(var key in arr2[i]){
arr1[idIndex][key] = arr2[i][key];
}
} else {
//insert
arr1.push(arr2[i]);
}
}
return arr1;
}
//Returns position in array that ID exists
function hasID(id, arr) {
for(var i = 0; i < arr.length; i ++) {
if(arr[i]['id'] === id)
{
return i;
}
}
return -1;
}
var combine = combineArrays(arr1, arr2);
output(combine);
/* pretty Print */
function output(inp) {
var str = JSON.stringify(inp, undefined, 4);
$('body').append($('<pre/>').html(str));
}
var arr1 = [{
id: 1,
name: 'fred',
title: 'boss'
}, {
id: 2,
name: 'jim',
title: 'nobody'
}, {
id: 3,
name: 'bob',
title: 'dancer'
}];
var arr2 = [{
id: 1,
wage: '300',
rate: 'day'
}, {
id: 2,
wage: '10',
rate: 'hour'
}, {
id: 4,
wage: '500',
rate: 'week'
}];
function combineArrays(arr1, arr2) {
for (var i = 0; i < arr2.length; i++) {
var idIndex = hasID(arr2[i]['id'], arr1);
if (idIndex >= 0) {
for (var key in arr2[i]) {
arr1[idIndex][key] = arr2[i][key];
}
} else {
arr1.push(arr2[i]);
}
}
return arr1;
}
function hasID(id, arr) {
for (var i = 0; i < arr.length; i++) {
if (arr[i]['id'] === id) {
return i;
}
}
return -1;
}
var combine = combineArrays(arr1, arr2);
output(combine);
/* pretty Print */
function output(inp) {
var str = JSON.stringify(inp, undefined, 4);
$('body').append($('<pre/>').html(str));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How about something along the lines of this:
function combineArrays(arr1, arr2, keyFunc) {
var combined = [],
keys1 = arr1.map(keyFunc),
keys2 = arr2.map(keyFunc),
pos1 = keys1.map(function (id) {
return keys2.indexOf(id);
}),
pos2 = keys2.map(function (id) {
return keys1.indexOf(id);
});
arr1.forEach(function (item, i) {
combined.push( $.extend(item, arr2[pos1[i]]) );
});
arr2.forEach(function (item, i) {
if (pos2[i] === -1) combined.push( item );
});
return combined;
}
used as
var combine = combineArrays(arr1, arr2, function (item) {
return item.id;
});
var arr1 = [
{ id: 1, name: 'fred', title: 'boss' },
{ id: 2, name: 'jim', title: 'nobody' },
{ id: 3, name: 'bob', title: 'dancer' }
];
var arr2 = [
{ id: 1, wage: '300', rate: 'day' },
{ id: 2, wage: '10', rate: 'hour' },
{ id: 4, wage: '500', rate: 'week' }
];
function combineArrays(arr1, arr2, keyFunc) {
var combined = [],
keys1 = arr1.map(keyFunc),
keys2 = arr2.map(keyFunc),
pos1 = keys1.map(function (id) {
return keys2.indexOf(id);
}),
pos2 = keys2.map(function (id) {
return keys1.indexOf(id);
});
arr1.forEach(function (item, i) {
combined.push( $.extend(item, arr2[pos1[i]]) );
});
arr2.forEach(function (item, i) {
if (pos2[i] === -1) combined.push( item );
});
return combined;
}
var combine = combineArrays(arr1, arr2, function (item) {
return item.id;
});
output(combine);
//
//
//
/* pretty Print */
function output(inp) {
var str = JSON.stringify(inp, undefined, 4);
$('body').append($('<pre/>').html(str));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Related

check the difference between two arrays of objects in javascript [duplicate]

This question already has answers here:
How to get the difference between two arrays of objects in JavaScript
(22 answers)
Closed 1 year ago.
I need some help. How can I get the array of the difference on this scenario:
var b1 = [
{ id: 0, name: 'john' },
{ id: 1, name: 'mary' },
{ id: 2, name: 'pablo' },
{ id: 3, name: 'escobar' }
];
var b2 = [
{ id: 0, name: 'john' },
{ id: 1, name: 'mary' }
];
I want the array of difference:
// [{ id: 2, name: 'pablo' }, { id: 3, name: 'escobar' }]
How is the most optimized approach?
I´m trying to filter a reduced array.. something on this line:
var Bfiltered = b1.filter(function (x) {
return x.name !== b2.reduce(function (acc, document, index) {
return (document.name === x.name) ? document.name : false
},0)
});
console.log("Bfiltered", Bfiltered);
// returns { id: 0, name: 'john' }, { id: 2, name: 'pablo' }, { id: 3, name: 'escobar' } ]
Thanks,
Robot
.Filter() and .some() functions will do the trick
var b1 = [
{ id: 0, name: 'john' },
{ id: 1, name: 'mary' },
{ id: 2, name: 'pablo' },
{ id: 3, name: 'escobar' }
];
var b2 = [
{ id: 0, name: 'john' },
{ id: 1, name: 'mary' }
];
var res = b1.filter(item1 =>
!b2.some(item2 => (item2.id === item1.id && item2.name === item1.name)))
console.log(res);
You can use filter to filter/loop thru the array and some to check if id exist on array 2
var b1 = [{ id: 0, name: 'john' }, { id: 1, name: 'mary' }, { id: 2, name: 'pablo' }, { id: 3, name: 'escobar' } ];
var b2 = [{ id: 0, name: 'john' }, { id: 1, name: 'mary' }];
var result = b1.filter(o => !b2.some(v => v.id === o.id));
console.log(result);
Above example will work if array 1 is longer. If you dont know which one is longer you can use sort to arrange the array and use reduce and filter.
var b1 = [{ id: 0, name: 'john' }, { id: 1, name: 'mary' }, { id: 2, name: 'pablo' }, { id: 3, name: 'escobar' } ];
var b2 = [{ id: 0, name: 'john' }, { id: 1, name: 'mary' }];
var result = [b1, b2].sort((a,b)=> b.length - a.length)
.reduce((a,b)=>a.filter(o => !b.some(v => v.id === o.id)));
console.log(result);
Another possibility is to use a Map, allowing you to bring down the time complexity to O(max(n,m)) if dealing with a Map-result is fine for you:
function findArrayDifferences(arr1, arr2) {
const map = new Map();
const maxLength = Math.max(arr1.length, arr2.length);
for (let i = 0; i < maxLength; i++) {
if (i < arr1.length) {
const entry = arr1[i];
if (map.has(entry.id)) {
map.delete(entry.id);
} else {
map.set(entry.id, entry);
}
}
if (i < arr2.length) {
const entry = arr2[i];
if (map.has(entry.id)) {
map.delete(entry.id);
} else {
map.set(entry.id, entry);
}
}
}
return map;
}
const arr1 = [{id:0,name:'john'},{id:1,name:'mary'},{id:2,name:'pablo'},{id:3,name:'escobar'}];
const arr2 = [{id:0,name:'john'},{id:1,name:'mary'},{id:99,name:'someone else'}];
const resultAsArray = [...findArrayDifferences(arr1,arr2).values()];
console.log(resultAsArray);

deleting an element in nested array using filter() function

I have been trying to delete an element with an ID in nested array.
I am not sure how to use filter() with nested arrays.
I want to delete the {id: 111,name: "A"} object only.
Here is my code:
var array = [{
id: 1,
list: [{
id: 123,
name: "Dartanan"
}, {
id: 456,
name: "Athos"
}, {
id: 789,
name: "Porthos"
}]
}, {
id: 2,
list: [{
id: 111,
name: "A"
}, {
id: 222,
name: "B"
}]
}]
var temp = array
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array[i].list.length; j++) {
temp = temp.filter(function(item) {
return item.list[j].id !== 123
})
}
}
array = temp
You can use the function forEach and execute the function filter for every array list.
var array = [{ id: 1, list: [{ id: 123, name: "Dartanan" }, { id: 456, name: "Athos" }, { id: 789, name: "Porthos" }] }, { id: 2, list: [{ id: 111, name: "A" }, { id: 222, name: "B" }] }];
array.forEach(o => (o.list = o.list.filter(l => l.id != 111)));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
To remain the data immutable, use the function map:
var array = [{ id: 1, list: [{ id: 123, name: "Dartanan" }, { id: 456, name: "Athos" }, { id: 789, name: "Porthos" }] }, { id: 2, list: [{ id: 111, name: "A" }, { id: 222, name: "B" }] }],
result = array.map(o => ({...o, list: o.list.filter(l => l.id != 111)}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could create a new array which contains elements with filtered list property.
const result = array.map(element => (
{
...element,
list: element.list.filter(l => l.id !== 111)
}
));
You can use Object.assign if the runtime you are running this code on does not support spread operator.
Array.filter acts on elements:
var myArray = [{something: 1, list: [1,2,3]}, {something: 2, list: [3,4,5]}]
var filtered = myArray.filter(function(element) {
return element.something === 1;
// true = keep element, false = discard it
})
console.log(filtered); // logs [{something: 1, list: [1,2,3]}]
You can use it like this:
var array = [{
id: 1,
list: [{
id: 123,
name: "Dartanan"
}, {
id: 456,
name: "Athos"
}, {
id: 789,
name: "Porthos"
}]
}, {
id: 2,
list: [{
id: 111,
name: "A"
}, {
id: 222,
name: "B"
}]
}]
for (var i = 0; i < array.length; ++i) {
var element = array[i]
// Filter the list
element.list = element.list.filter(function(listItem) {
return listItem.id !== 111 && listItem.name !== 'A';
})
}
console.log(array)

using javascript filter() with a flag

I want to return arr2 but want to prompt the user whether there's changes or not by comparing it with arr. with below's approach, I got id of undefined if arr2 have any missing item.
var arr = [{
id: 1,
name: 'something'
}, {
id: 2,
name: 'something2'
}]
var arr2 = [{
id: 1,
name: 'something'
}]
var result = arr.filter(function(obj, i) {
return obj.id == arr2[i].id;
});
document.write(JSON.stringify(result))
The problem in your code is that arr[1] is undefined and you are trying to get id property of undefined. Now what you can do is, get id's in array then get index and check based on that in filter.
var arr = [{
id: 1,
name: 'something'
}, {
id: 2,
name: 'something2'
}]
var arr2 = [{
id: 1,
name: 'something'
}];
var arrIds = arr2.map(function(v) {
return v.id;
});
var result = arr.filter(function(obj) {
var i = arrIds.indexOf(obj.id);
return i > -1 &&
obj.name == arr2[i].name; // check name property here
});
document.write(JSON.stringify(result))
Loop through arr2 in a callback of the .filter to test each item of arr2.
var arr = [{
id: 1,
name: 'something'
}, {
id: 2,
name: 'something2'
}]
var arr2 = [{
id: 1,
name: 'something'
}, {
id: 5,
name: 'something'
}, {
id: 8,
name: 'something'
}];
var isValInArr = function(arr, key, val) {
for (var i = 0, len = arr.length; i < len; i++) {
if (arr[i][key] === val) {
return true;
}
}
return false;
};
var result = arr.filter(function(obj, i) {
return isValInArr(arr2, 'id', obj.id);
});
document.write(JSON.stringify(result))

Search deep in array and delete

I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.
This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.

Merging/extend javascript object arrays based on join of a key property in each

I am wanting to merge the following object arrays, by first joining on the id property
var arr1 = [{
id: 1,
name: 'fred',
title: 'boss'
},{
id: 2,
name: 'jim',
title: 'nobody'
},{
id: 3,
name: 'bob',
title: 'dancer'
}];
var arr2 = [{
id: 1,
wage: '300',
rate: 'day'
},{
id: 2,
wage: '10',
rate: 'hour'
},{
id: 3,
wage: '500',
rate: 'week'
}];
So the result would be
[{
id: 1,
name: 'fred',
title: 'boss',
wage: '300',
rate: 'day'
},{
id: 2,
name: 'jim',
title: 'nobody',
wage: '10',
rate: 'hour'
},{
id: 3,
name: 'bob',
title: 'dancer',
wage: '500',
rate: 'week'
}]
I would like to avoid using js frameworks (if possible), although ExtJs is already part of the project.
AT the moment I have a loop with an inner loop that if the keys match it copies the properties and breaks out of the inner loop to start the next outer loop.
Any better suggestions?
Like this?
var combined = [];
function findSecond(id,second){
for (var i=0;i<second.length;i++){
if(second[i].id === id){
return second[i];
}
}
return null
}
while (el = arr1.pop()){
var getSec = findSecond(el.id,arr2);
if (getSec){
for (var l in getSec){
if (!(l in el)) {
el[l] = getSec[l];
}
}
combined.push(el);
}
}
If the arrays have the same length, and the id's are equal, a simpler merge will do:
function merge(a1,a2) {
var i = -1;
while ((i = i+1)<a1.length) {
for (var l in a2[i]) {
if (!(l in a1[i] )) {
a1[i][l] = a2[i][l];
}
}
}
return a1;
}
Here's a working example
[Edit 2016/07/30] Added a snippet using more functional approach and, based on #djangos comment, an extra method to combine both arrays.
(function() {
var alert = function(str) {document.querySelector('#result').textContent += str + '\n';};
var arrays = getArrays();
alert('Combine on id (shared id\'s):')
alert(JSON.stringify(combineById(arrays.arr1, arrays.arr2), null, ' '));
alert('\nCombine on id (all id\'s):')
alert(JSON.stringify(combineBothById(arrays.arr1, arrays.arr2), null, ' '));
// for combineBothById the parameter order isn't relevant
alert('\nCombine on id (all id\'s, demo parameter order not relevant):')
alert(JSON.stringify(combineBothById(arrays.arr2, arrays.arr1), null, ' '));
// combine first array with second on common id's
function combineById(arr1, arr2) {
return arr1.map(
function (el) {
var findInB = this.filter(function (x) {return x.id === el.id;});
if (findInB.length) {
var current = findInB[0];
for (var l in current) {
if (!el[l]) {el[l] = current[l];}
}
}
return el;
}, arr2);
}
// combine first array with second on all id's
function combineBothById(arr1, arr2) {
var combined = arr1.map(
function (el) {
var findInB = this.filter(function (x) {return x.id === el.id;});
if (findInB.length) {
var current = findInB[0];
for (var l in current) {
if (!el[l]) {el[l] = current[l];}
}
}
return el;
}, arr2);
combined = combined.concat(arr2.filter(
function (el) {
return !this.filter(function (x) {return x.id === el.id;}).length;
}, combined));
return combined;
}
function getArrays() {
return {
arr1: [{
id: 1,
name: 'fred',
title: 'boss'
}, {
id: 2,
name: 'jim',
title: 'nobody'
}, {
id: 3,
name: 'bob',
title: 'dancer'
}],
arr2: [{
id: 1,
wage: '300',
rate: 'day'
}, {
id: 2,
wage: '10',
rate: 'hour'
}, {
id: 4,
wage: '500',
rate: 'week'
}]
};
}
}());
<pre id="result"></pre>
You can merge two arrays by id column with Alasql library:
var res = alasql('SELECT * FROM ? arr1 JOIN ? arr2 USING id', [arr1,arr2]);
Try this example at jsFiddle.
try this...
var arr1 = [{
id: 1,
name: 'fred',
title: 'boss'
},{
id: 2,
name: 'jim',
title: 'nobody'
},{
id: 3,
name: 'bob',
title: 'dancer'
}];
var arr2 = [{
id: 1,
wage: '300',
rate: 'day'
},{
id: 2,
wage: '10',
rate: 'hour'
},{
id: 3,
wage: '500',
rate: 'week'
}];
let arr5 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));
console.log(arr5)

Categories

Resources