Compare two array of objects based on their id's - javascript

I am trying to compare two arrays of objects based on their IDs and return a boolean. I have filtered out items from the both arrays based on is_selected === 1. If IDs of both arrays are same will return isModified false or it will return true.
Both of these are an array of checkboxes. The initial array is what I am getting from the backend and the current is modified by the user. The current array length can be changed by changing checkboxes.
With my current approach for loop is not executing from the second time.
function cancel() {
const initialArr = [
{id: 8, name: "Celery", is_selected: 1},
{id: 9, name: "Crustaceans", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
];
const currentArr = [
{id: 8, name: "Celery", is_selected: 1},
{id: 4, name: "Mustard", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
];
let isModified!: boolean;
for (let index = 0; index < currentArr.length; index++) {
const id = currentArr[index].id;
isModified = initialArr.some((o2) => o2.id !== id);
if (isModified) break;
}
if(isModified){
alert('are you sure?');
} else {
console.log('exit the page');
}
}

There's no issue with the for loop in your code. There are few changes that you could do to improve it
Use a map instead of an array for faster lookups. Running an iteration on array everytime is slow, and it will only increase as the size of arr grows.
Check-in each iteration if the value is changed in the currentArr.
You also need to establish what will happen when there is a new item in the currentArr(i.e with new id), will it be counted as modified or as not modified?
Assuming the answer to the above question is yes, the below code should work
function cancel() {
const initialArr = [
{id: 8, name: "Celery", is_selected: 1},
{id: 9, name: "Crustaceans", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
];
const initalArrMap = initialArr.reduce((prev, current) => {
return {...prev, [current.id]: current}
}, {});
const currentArr = [
{id: 8, name: "Celery", is_selected: 1},
{id: 9, name: "Mustard", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
{id: 9, name: "Fish", is_selected: 1},
];
let isModified = currentArr.length !== initialArr.length;
if(!isModified){
for (let index = 0; index < currentArr.length; index++) {
const id = currentArr[index].id;
isModified = initalArrMap[id] ? initalArrMap[id].is_selected != currentArr[index].is_selected : true;
if (isModified) break;
}
}
if(isModified){
alert('are you sure?');
} else {
console.log('exit the page');
}
}
cancel();
Note: I am only checking if is_selected is changed to determine if the currentArr is changed.
Edit: Added a check in case currentArr and initialArr length are not equal

The problem is using initialArr.some((o2) => o2.id !== id).
The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false.
What do you want to check? If initialArr does not have currentArr ids, it means my currentArr is modified, so you need to try this one:
!initialArr.some((o2) => o2.id === id)
const initialArr = [
{ id: 8, name: "Celery", is_selected: 1 },
{ id: 9, name: "Crustaceans", is_selected: 1 },
{ id: 2, name: "Eggs", is_selected: 1 },
{ id: 6, name: "Fish", is_selected: 1 }
];
const currentArr = [
{ id: 8, name: "Celery", is_selected: 1 },
{ id: 4, name: "Mustard", is_selected: 1 },
{ id: 2, name: "Eggs", is_selected: 1 },
{ id: 6, name: "Fish", is_selected: 1 }
];
let isModified = false;
for (let index = 0; index < currentArr.length; index++) {
const id = currentArr[index].id;
isModified = !initialArr.some((o2) => o2.id === id);
if (isModified)
break;
}
if (isModified) {
alert('are you sure?');
} else {
console.log('exit the page');
}

It will compare each element of first array with all elements of 2nd array
sorting key will make sure it gives correct result if sequence of keys are changed .
it will break the loop as soon as it finds the first different object.
function cancel() {
const a = [
{id: 8, name: "Celery", is_selected: 1},
{id: 9, name: "Crustaceans", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
];
const b = [
{id: 8, name: "Celery", is_selected: 1},
{id: 4, name: "Mustard", is_selected: 1},
{id: 2, name: "Eggs", is_selected: 1},
{id: 6, name: "Fish", is_selected: 1},
];
let isModified;
let ctr =0;
for (let index = 0; index < a.length; index++) {
for(let j=0; j<b.length; j++){
var aKeys = Object.keys(a[index]).sort();
var bKeys = Object.keys(b[j]).sort();
if(aKeys.toString()!== bKeys.toString()){
ctr++;
break;
}
}
}
if(ctr){
alert('Modified');
} else {
console.log('not modified');
}
}
cancel();

The reason it was returning true for every item in the array is not because of logic error but syntax error, check your array of Objects they don't have commas that separate array elements

Related

Remove equal objects from two arrays

I have the following problem in my ReactJs application. Let's say I have two arrays like this:
var cart = [
{id: 1, name: "item1"},
{id: 2, name: "item2"},
];
var productsArr = [
{proId: 1, category: 'cat1'},
{proId: 5, category: 'cat7'},
];
Is it possible to compare these 2 arrays and find any objects in productsArr which cart's id quals productsArr's proId and remove that object from only productsArr?
(If so, as I explained in this example, productsArr[0] should be removed.)
Thanks in advance.
You can use Array#filter in conjunction with Array#some.
var cart = [
{id: 1, name: "item1"},
{id: 2, name: "item2"},
];
var productsArr = [
{proId: 1, category: 'cat1'},
{proId: 5, category: 'cat7'},
];
productsArr = productsArr.filter(({proId})=>!cart.some(({id})=>proId === id));
console.log(productsArr);
var cart = [
{id: 1, name: "item1"},
{id: 2, name: "item2"},
];
var productsArr = [
{proId: 1, category: 'cat1'},
{proId: 5, category: 'cat7'},
];
for (var i = 0; i<productsArr.length; i++) {
if (cart.find(item => item.id === productsArr[i].proId)) {
productsArr.splice(i,1);
i--;
}
}
console.log(productsArr);

display entire array after moving values of it to another array

I have an array containing objects where there is a rpId key in some of the objects. The goal is to separate/move the objects that return undefined to a separate array and remove them out of the first array.
e.g.:
results = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
goal: results = [{id: 2, rpId: 1076}, {id: 4, rpId: 303}] and stations = [{id: 1}, {id: 3}]
My current approach can be seen below. As visible, I get a wrong array1 because it contains an object with a rpId, plus array2 returns the keys of the object and I'd like to read the entire object, not just the "undefined" of the key.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const array2 = [];
const mapping = array1.map((e) => e.rpId);
console.log("mapping",mapping);
mapping.forEach(function(elem, index){
elem === undefined ? array2.push(elem) && array1.splice(index, elem === undefined) && console.log(elem): console.log("defined", elem);
}),
console.log("1", array1); // [{ id: 2, rpId: 1076 }, { id: 3 }]
console.log("2", array2); // [undefined, undefined]
Just check if the rpId property is undefined in each element.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const array2 = [];
array1.forEach(function(elem, index){
if(elem.rpId === undefined)
array2.push(elem) && array1.splice(index, 1)
});
console.log(array1);
console.log(array2);
One can also use Array#filter or push elements into two separate arrays based on the condition for better performance.
const array1 = [{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}];
const yes = [], no = [];
array1.forEach(elem=>(elem.rpId!==undefined?yes:no).push(elem));
console.log(yes);
console.log(no);
You can use filter too:
let results = [
{ id: 1 },
{ id: 2, rpId: 1076 },
{ id: 3 },
{ id: 4, rpId: 303 },
];
const stations = results.filter((c) => !c.rpId);
results = results.filter((c) => c.rpId);
console.log("stations", stations);
console.log("results", results);
const GiveItACreativeName = (arr) => {
const result = []
const stations = []
arr.forEach((el) => {
if('rpId' in el) result.push(el);
else stations.push(el);
});
return {result, stations}
}
console.log(
GiveItACreativeName([{id: 1}, {id: 2, rpId: 1076}, {id: 3}, {id: 4, rpId: 303}])
);

Remove same Values from array of Object

I want to remove same object from array by comparing 2 arrays.
Sample Data:
arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
];
arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
let newArray = []; // new array with with no same values it should be unique.
arr1.map((val, i)=>{
arr2.map((val2)=>{
if(val.id == val2.id){
console.log('Matched At: '+ i) // do nothing
}else{
newArray.push(val);
}
})
})
console.log(newArray); // e.g: [{id: 2, name: "b"}, {id: 3, name: "c"},];
Array.filter combined with not Array.some.
The trick here is also to not some,..
const arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
], arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
const newArray=arr1.filter(a=>!arr2.some(s=>s.id===a.id));
console.log(newArray);
.as-console-wrapper { max-height: 100% !important; top: 0; }
As mentioned in comments the question could be interpreted slightly differently. If you also want the unqiue items from arr2, you basically just do it twice and join. IOW: check what not in arr2 is in arr1, and then check what not in arr1 that's in arr2.
eg..
const notIn=(a,b)=>a.filter(f=>!b.some(s=>f.id===s.id));
const newArray=[...notIn(arr1, arr2), ...notIn(arr2, arr1)];
Update 2:
Time complexity, as mentioned by qiAlex there is loops within loops. Although some will short circuit on finding a match, if the dataset gets large things could slow down. This is were Set and Map comes in.
So to fix this using a Set.
const notIn=(a,b)=>a.filter(a=>!b.has(a.id));
const newArray=[
...notIn(arr1, new Set(arr2.map(m=>m.id))),
...notIn(arr2, new Set(arr1.map(m=>m.id)))
];
const isInArray = (arr, id, name) => arr.reduce((result, curr) => ((curr.name === name && curr.id === id) || result), false)
const newArray = arr1.reduce((result, curr) => (isInArray(arr2, curr.id, curr.name) ? result : result.concat(curr)), [])
You can update you code using filter() method, instead of using .map() method like:
const arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
], arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
let newArray = []; // new array with with no same values it should be unique.
newArray = arr1.filter(function(a) {
for(var i=0; i < arr2.length; i++){
if(a.id == arr2[i].id) return false;
}
return true;
});
console.log(newArray);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You check each element in first array whether its id lies in the second array by using Array.prototype.some. If the element is not present then only yield it.
const arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
];
const arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
const result = arr1.filter(x => !arr2.some(y => y.id === x.id));
console.log(result);
I think a simple comparer can works for getting differences and then concat them.
with this method you dont need to check which array is bigger.
arr1 = [ {id: 1, name: "a"}, {id: 2, name: "b"}, {id: 3, name: "c"}, {id: 4, name: "d"}];
arr2 = [ {id: 1, name: "a"}, {id: 4, name: "d"},];
function localComparer(b){
return function(a){
return b.filter(
function(item){
return item.id == a.id && item.name == a.name
}).length == 0;
}
}
var onlyInArr1 = arr1.filter(localComparer(arr2));
var onlyInArr2 = arr2.filter(localComparer(arr1));
console.log(onlyInArr1.concat(onlyInArr2));
We can filter values by checking whether some element is not contained in current array:
const result = arr1.reduce((a, c) => {
if (!arr2.some(a2 => a2.id === c.id))
a.push(c);
return a;
}, [])
An example:
let arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
];
let arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
const result = arr1.reduce((a, c) => {
if (!arr2.some(a2 => a2.id === c.id))
a.push(c);
return a;
}, [])
console.log(result);
Try this one -
const arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
];
const arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
const arr3 = [...arr1, ...arr2];
const mySubArray = _.uniq(arr3, 'id');
console.log(mySubArray);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
So many loops in every answer.
Complexity of the code my answer is 2N,
Idea is:
to merge arrays.
first loop - mark duplicates somehow
second loop - filter duplicates out
arr1 = [
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
];
arr2 = [
{id: 1, name: "a"},
{id: 4, name: "d"},
];
let newArray = [...arr1, ...arr2].reduce((acc, item, index) => {
acc.items.push(item);
if (typeof acc.map[item.id] !== 'undefined') {
acc.items[acc.map[item.id]] = null;
acc.items[index] = null;
}
acc.map[item.id] = index;
return acc
}, {map: {}, items: []}).items.filter(item => !!item)
console.log(newArray);

How to transform this object using Object.keys and filtering

I have an object like:
const arr = {
[1]: [{id: 1, category: 1}, {id: 2, category: 2}],
[2]: [{id: 3, category: 2}, {id: 4, category: 2}],
[3]: [{id: 5, category: 3}, {id: 6, category: 3}]
}
Is it possible to move for example {id: 2, category: 2} to another place to have:
const arr = {
[1]: [{id: 1, category: 1}],
[2]: [{id: 2, category: 2}, {id: 3, category: 2}, {id: 4, category: 2}],
[3]: [{id: 5, category: 3}, {id: 6, category: 3}]
}
items with category = 2 should be in the array[2], 3 in 3 etc. It's possible to do that with Object.keys and filtering somehow? Or maybe there is a better solution?
You can use Object.keys() to iterate through arr, then using array#reduce you can group your array on the category value.
const arr = {[1]: [{id: 1, category: 1}, {id: 2, category: 2}], [2]: [{id: 3, category: 2}, {id: 4, category: 2}], [3]: [{id: 5, category: 3}, {id: 6, category: 3}]},
result = Object.keys(arr).reduce((r,k) => {
arr[k].forEach(({id, category}) => {
r[category] = r[category] || [];
r[category].push({id, category});
})
return r;
},{});
console.log(result);
You can use array#concat all the values of your object, then using array#reduce group object based on category.
const arr = {[1]: [{id: 1, category: 1}, {id: 2, category: 2}], [2]: [{id: 3, category: 2}, {id: 4, category: 2}], [3]: [{id: 5, category: 3}, {id: 6, category: 3}]},
result = [].concat(...Object.values(arr)).reduce((r,{id, category}) => {
r[category] = r[category] || [];
r[category].push({id, category});
return r;
},{});
console.log(result);
Yes, the Object.keys can be used to traverse the array. Then, using Array.splice and Array.push, you can remove item and push it to proper place. Please check the following code:
const arr = {
[1]: [{id: 1, category: 1}, {id: 2, category: 2}, {id: 7, category: 3}],
[2]: [{id: 3, category: 2}, {id: 4, category: 2}],
[3]: [{id: 5, category: 3}, {id: 6, category: 3}]
};
Object.keys(arr).forEach(function(k){
for(var j = 0; j < arr[k].length;j++) {
if(arr[k][j].category!=k) {
var removedItem = arr[k].splice(arr[k].indexOf(j), 1);
var pushedArr = arr[removedItem[0].category];
if(pushedArr) {
pushedArr.push(removedItem[0]);
j--;
}
}
}
});
console.log(arr);

Javascript concat and overwrite where element has the same id

Is it possible to concat two arrays with objects and let the second array overwrite the first array where they have the same id:
// array 1
[
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
// array 2:
[
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
// out:
[
{id: 1, name: "newFoo"}, // overwriten by array 2
{id: 2, name: "bar"}, // not changed (from array 1)
{id: 3, name: "baz"}, // not changed (from array 1)
{id: 4, name: "y"}, // added (from array 2)
{id: 5, name: "z"} // added (from array 2)
]
If it is possible I would like to do this without the use of third party libraries
var a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var b = [
{id: 1, name: "fooboo"},
{id: 4, name: "bar"},
{id: 5, name: "baz"}
];
/* iterate through each of b, if match found in a, extend with that of a. else push into b ...*/
b.forEach(m => {
var item = a.find(n => n.id === m.id);
if(item) { return Object.assign(item, m); }
a.push(m);
});
console.log(a);
You can do
let arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
let arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
let result = arr1.concat(arr2).reduce((a, b) => {
a[b.id] = b.name;
return a;
},{})
result = Object.keys(result).map(e => {
return {id : e, name : result[e]};
});
console.log(result);
Explanation
I am using the property of objects that they don't keep duplicate keys, so for an array concated together, I reduce it to an object with id as it's key and name as its value, hence overriding all duplicates. In the next step I converted this back into an array.
Check you my solution. There is no "rewrite", i just use a second array as base and don't write value if it has same id.
let a = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
let b = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
let duplicateId;
a.forEach(aitem => {
duplicateId = false;
b.forEach(bitem => {
if (aitem.id === bitem.id)
duplicateId = true;
});
if (!duplicateId)
b.push(aitem);
});
console.log(b);
Maybe you can use Object.assign and Object.entries to achieve, lets say:
const arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
]
const arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
]
const obj3 = Object.entries(Object.assign({}, ...arr1, arr2))
.map(([prop, value]) => ({[prop]:value}));
Example:
https://jsfiddle.net/0f75vLka/
Another option would be to convert arrays to map with id as key then merge the objects and then convert it back to array.
var arr1 = [
{id: 1, name: "foo"},
{id: 2, name: "bar"},
{id: 3, name: "baz"}
];
var arr2 = [
{id: 1, name: "newFoo"},
{id: 4, name: "y"},
{id: 5, name: "z"}
];
function arr2map(arr) {
var map = {};
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
map[item.id] = item;
}
return map;
}
function map2arr(map) {
var arr = [];
for (var i in map) {
arr.push(map[i]);
}
return arr;
}
var arr1m = arr2map(arr1);
var arr2m = arr2map(arr2);
var arr3m = map2arr( Object.assign({}, arr1m, arr2m) );
//output
alert(JSON.stringify(arr3m));

Categories