How to find elements in array2 that are not in array1? - javascript

I have two arrays:
var a1 = [ { ID: 2, N:0 }, { ID: 1, N:0 } ];
var a2 = [ { ID: 1, N:0 }, { ID: 2, N:0 }, { ID: 3, N:0 } ];
I need to get all elements that are on a2 but not in a1. An element here is distinct of another only by the property ID, the other properties should be ignored. And I cannot guarantee the order of the elements on the arrays. Meaning the result for this example should be:
var result = [ { ID: 3, N:0 } ]; // result for the example above
How can I do this in an efficient way? (I will be comparing arrays from 500 to 5,000 length)

To do this efficiently, you need to build an index of the items that are already in a1 so you can cycle through a2 and compare each one to the index to see if it's already been seen or not. One can use a javascript object for an index. Cycle through a1 and put all its IDs into the index. Then cycle through a2 and collect any items whose ID does not appear in the index.
function findUniques(testItems, baseItems) {
var index = {}, i;
var result = [];
// put baseItems id values into the index
for (i = 0; i < baseItems.length; i++) {
index[baseItems[i].ID] = true;
}
// now go through the testItems and collect the items in it
// that are not in the index
for (i = 0; i < testItems.length; i++) {
if (!(testItems[i].ID in index)) {
result.push(testItems[i]);
}
}
return(result);
}
var a1 = [ { ID: 2, N:0 }, { ID: 1, N:0 } ];
var a2 = [ { ID: 1, N:0 }, { ID: 2, N:0 }, { ID: 3, N:0 } ];
var result = findUniques(a2, a1);
// [{"ID":3,"N":0}]
Working demo: http://jsfiddle.net/jfriend00/uDEtg/

The same question has been posted a few times, have a look here:
JavaScript array difference
Most solutions are given through 'native' javascript however. I sometimes prefer to use underscore.js, since I build a lot of things using backbone.js and underscore is a dependency for Backbone. So I can use its awesome utilities. You might consider loading them in:
http://documentcloud.github.com/underscore/
var a1 = [ { ID: 2, N:0 }, { ID: 1, N:0 } ];
var a2 = [ { ID: 1, N:0 }, { ID: 2, N:0 }, { ID: 3, N:0 } ];
var from, to;
if(a1 > a2){
from = a1
to = a2
} else {
from = a2
to = a1
}
var a3 = _.filter(from, function(obj){
var compare = _.find(to, function(obj2){ return obj.ID === obj2.ID });
return compare === undefined
});
console.log(a3);
I first determined the longest array, I do this because I want to compare as many objects as possible to the shorter list. Otherwise we'd 'forget' some.
Then I simply use filter and find in the underscore.js library to return the objects that aren't in the shorter array, but ARE in the longer array.
If both arrays are of equal length it is fine too, because then we'd compare all of the items to all of the others.

Related

JavaScript | How can I remove an element of an array using it's value instead of it's index?

const currentMaterialsId = [1,2,3,4,5]
const materials = {
0: {
id: 1
},
1: {
id: 2
},
2: {
id: 3
},
3: {
id: 4
},
4: {
id: 5
}
}
I am trying to remove an element in the currenMaterialsId array but when I use the index of the materials object, things don't go as planned. If I use the id as the start number in splice, it still uses that number and searches for the matching index in the array instead of the value. Please help.
here's what I have at the moment.
let sortedMaterialIndex = currentMaterialsId.sort()
sortedMaterialIndex.splice(materialIndex, 1)
dispatch(removeElementCurrentMaterialsArray(selectedSheet,
sortedMaterialIndex))
ok I'm sorry it wasn't clear guys.
What I am trying to do is remove an element in currentMaterialsId that has the same value as the id in the object materials. However, when I use the id from materials as a starting number, for example
const materialId = dashboard.sheets[selectedSheet].materialProperties[materialIndex].id
currentMaterialsId.splice(materialId, 1)
it searches currentMaterialsId array for an index that matches the passed starting number(materialId), which is what I do not want.
so let's say I want to delete 2 from currentMaterialsId, could I use splice? and if I use splice, what should I pass as a starting number?
I hope this makes my question clearer.
Thanks for the responses!
What I am trying to do is remove an element in currentMaterialsId that
has the same value as the id in the object materials.
could I use splice?
You appear to be trying to do something like this:
so.js:
const materials = {
'0': { id: 1 },
'1': { id: 2 },
'2': { id: 3 },
'3': { id: 4 },
'4': { id: 5 }
};
console.log(materials);
// id from materials
let i = 1;
console.log(i);
let id = materials[i].id;
console.log(id);
function removeMaterialsId(id, materialsId) {
for (let i = 0; i < materialsId.length; i++) {
if (materialsId[i] === id) {
materialsId.splice(i--, 1);
}
}
}
let materialsId = [];
// remove materialsId elements with id from materials
console.log();
materialsId = [ 1, 2, 3, 4, 5 ];
console.log(id, materialsId);
removeMaterialsId(id, materialsId);
console.log(materialsId);
// remove materialsId elements with id from materials
console.log();
materialsId = [ 1, 2, 2, 3, 4, 2, 5 ];
console.log(id, materialsId);
removeMaterialsId(id, materialsId);
console.log(materialsId);
$ node so.js
{
'0': { id: 1 },
'1': { id: 2 },
'2': { id: 3 },
'3': { id: 4 },
'4': { id: 5 }
}
1
2
2 [ 1, 2, 3, 4, 5 ]
[ 1, 3, 4, 5 ]
2 [ 1, 2, 2, 3, 4, 2, 5 ]
[ 1, 3, 4, 5 ]
$
First off, perhaps you want to store your objects in an array, like this(?):
const materials = [
{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
},
{
id: 5
}
];
Then you can remove from array using filter:
const materialToRemove = { id: 1 }
const materialsWithOneRemoved = materials
.filter(material => material.id !== materialToRemove.id);
Note that filter creates a new array, it does not change the existing array. You can however overwrite the existing array with a new one if you want to:
// materials like above, but with let instead of const
let materials = ...
const materialToRemove = { id: 1 }
materials = materials
.filter(material => material.id !== materialToRemove.id);
If you want to have your objects in an object like you have in your question, you need to first convert it to an array before you can filter. You can do that using e.g. Object.values.
Your question is far from clear, but indexOf may be a solution:
const sortedMaterialIndex = currentMaterialsId.sort();
const index = sortedMaterialIndex.indexOf(materialIndex);
if (index > -1) {
sortedMaterialIndex.splice(index, 1);
}
See How can I remove a specific item from an array?
I would recommend using the filter array function to achieve what you want.
let idToRemove = 1
let filteredMaterials = materials.filter((v) => v.id !== idToRemove);
console.log(filteredMaterials)

problems with for loop inside another for loop Javascript

I have problems in going through these two for loops, I need to get the same elements from the first array within the cycle, but the values ​​are being repeated. I know that they are repeated depending on the data of the second array.
I tried to make comparisons but I could not get the result I want.
var array = [
{
grouper: 1
},
{
grouper: 2
},
{
grouper: 3
},
{
grouper: 4
},
];
var array2 = [
{
value: 1,
grouper: 1,
status: 100
},
{
value: 2,
grouper: 2,
status: 100
},
{
value: 3,
grouper: 3,
status: 100
}
];
for(var i = 0; i<array.length; i++){
for(var j = 0; j<array2.length; j++){
if(array2[j].grouper == array[i].grouper){
console.log(array[i].grouper+'-'+array2[j].value);
}
}
}
This is the result I want, I need all the groupers from the first array and the values from the second array:
1-1
2-2
3-3
4-
The grouper 4, does not have value, but I need to show it.
I need the second array because I'm going to compare with the data from the second array
I do not know if I am doing the process wrong. I hope you can help me.
You could simply track if there was a match (variable shown), and if there were not any, display a "half" line:
var array = [{grouper: 1},{grouper: 2},{grouper: 3},{grouper: 4},];
var array2 = [
{value: 1, grouper: 1, status: 100},
{value: 2, grouper: 2, status: 100},
{value: 3, grouper: 3, status: 100}
];
for(var i = 0; i<array.length; i++){
var shown=false;
for(var j = 0; j<array2.length; j++){
if(array2[j].grouper == array[i].grouper){
console.log(array[i].grouper+'-'+array2[j].value);
shown=true;
}
}
if(!shown){
console.log(array[i].grouper+"-");
}
}
First of all, with the example you provided I believe you want to get back:
1,2,3
There is no 4th object inside of array2, so your conditional (array2[j].grouper == array[i].grouper will never evaluate to true.
The question here is whether you are always comparing the same indexes? In this example, you're comparing array[0] to array2[0] to see if grouper in array equals grouper in array2... that's it????
In that case you just do one loop:
for (var i = 0; i < array.length; i++) {
if (array[i].grouper == array2[i].grouper) {
console.log(array[i].grouper+'-'+array2[j].value);
}
}
#FabianSierra ... with your provided example one just needs to handle the not fulfilled if clause/condition in the most inner loop.
A more generic approach additionally might take into account changing field names (keys). Thus a function and Array.reduce / Array.find based approach provides better code reuse. An example implementation then might look similar to that ...
var array = [{ // in order.
grouper: 1
}, {
grouper: 2
}, {
grouper: 3
}, {
grouper: 4
}];
var array2 = [{ // not in the order similar to `array`.
value: 22,
grouper: 2,
status: 200
}, {
value: 33,
grouper: 3,
status: 300
}, {
value: 11,
grouper: 1,
status: 100
}];
function collectRelatedItemValuesByKeys(collector, item) {
var sourceKey = collector.sourceKey;
var targetKey = collector.targetKey;
var targetList = collector.targetList;
var resultList = collector.result;
var sourceValue = item[sourceKey];
var targetValue;
var relatedItem = targetList.find(function (targetItem) {
return (targetItem[sourceKey] === sourceValue);
});
if (typeof relatedItem !== 'undefined') {
targetValue = relatedItem[targetKey];
} else if (typeof targetValue === 'undefined') {
targetValue = ''; // `relatedItem` does not exist.
}
resultList.push([sourceValue, targetValue].join('-'));
return collector;
}
var resultList = array.reduce(collectRelatedItemValuesByKeys, {
sourceKey: 'grouper',
targetKey: 'value',
targetList: array2,
result: []
}).result;
console.log('resultList : ', resultList);
resultList = array.reduce(collectRelatedItemValuesByKeys, {
sourceKey: 'grouper',
targetKey: 'status',
targetList: array2,
result: []
}).result;
console.log('resultList : ', resultList);
.as-console-wrapper { max-height: 100%!important; top: 0; }

create element at index position if index does not exist in array

I have an array objects that hold an id and a name
const stages = [{
id: 1,
name: ''
}, {
id: 2,
name: ''
}, {
id: 3,
name: ''
}, {
id: 4,
name: ''
}, {
id: 5,
name: ''
}, {
id: 6,
name: ''
}, {
id: 7,
name: ''
}, {
id: 8,
name: ''
}];
Further I have an array that holds numbers.
const indexPositions = [0, 1, 2, 2, 2, 3, 2, 0];
I want to create a third array that holds arrays. Each number in distances represents the index of the current array within the array.
If the current array does not exist yet I want to create it first. Obviously I have to create new arrays until I get to this index position.
Example:
My array is empty at start. The first index position is 0 so I have to create a new array for this. The next index position is 3 so I have to create more arrays until I have 4 arrays.
All I want to do is to push the stage to its correct level index position. The result of this example would be
const levels = [
[stage1, stage8],
[stage2],
[stage3, stage4, stage5, stage7],
[stage6]
];
Currently my code looks this
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length;
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
I get this error Uncaught TypeError: Cannot read property 'push' of undefined and this happens because the indexPosition is out of bounds. If the positionDifference is 0 no array gets created but in the beginning the array is empty.
I tried setting levels.length to -1 if it is 0 but I still get the error if the difference is 1, I create one array at position 0 and want to access position 1.
How can I create an empty array if it does not exist?
While I do not fully understand what you want to do, checking existence of an array element is simple, one way of doing that is coercing it to boolean:
const thing=[];
function addElem(where,what){
if(!thing[where]) // <- here
thing[where]=[];
thing[where].push(what);
}
addElem(2,1);
addElem(2,2);
addElem(2,3);
addElem(5,1);
console.log(thing);
(The indices are deliberately non-continuous, because that does not matter: JavaScript arrays are sparse)
You could use a single loop and add an array for the index if not exists. Then push the wanted value.
var stages = [{ id: 1, name: '' }, { id: 2, name: '' }, { id: 3, name: '' }, { id: 4, name: '' }, { id: 5, name: '' }, { id: 6, name: '' }, { id: 7, name: '' }, { id: 8, name: '' }],
indexPositions = [0, 1, 2, 2, 2, 3, 2, 0],
result = stages.reduce((r, o, i) => {
var index = indexPositions[i];
r[index] = r[index] || []; // take default value for falsy value
r[index].push('stage' + o.id); // instead of string take object
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You actually were very close! You have a very small issue in your code.
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length + 1; //YOU DID NOT ADD 1 HERE
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
When you were calculating the positionDifference, you did not add 1 causing the problem when indexPosition equaled 0 and the for loop did not run and no new arrays were pushed. Just adding one fixed the problem :-)

How to combine _.map and _.filter in a more efficient way?

I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to merge two dictionaries in javascript

I have two arrays of objects:
Array1:
var myArr1 = [];
myArr1["1"]={any:1,some:1};
myArr1["2"]={any:2,some:2};
myArr1["3"]={any:3,some:3};
Array2:
var myArr2 = [];
myArr2["1"]={other:1};
myArr2["2"]={other:2};
And I want them to be merged by their keys into a new Attribute, so the result will be:
[
{any:1,some:1,myNewAttribute:{other:1}},
{any:2,some:2,myNewAttribute:{other:2}},
{any:3,some:3,myNewAttribute:{other:3}}
]
I tried to achieve it with lodash's _.merge() but I failed miserably. _.merge only adds the second array after the first, but does not match their keys / ids.
You could map the second array to a new property and merge later.
With lodash
var data1 = [{ any: 1, some: 1 }, { any: 2, some: 2 }, { any: 3, some: 3 }],
data2 = [{ other: 1 }, { other: 2 }, { other: 3 }];
console.log(_.merge(data1, _.map(data2, x => ({ myNewAttribute: x }))));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
With ES6, without lodash
var data1 = [{ any: 1, some: 1 }, { any: 2, some: 2 }, { any: 3, some: 3 }],
data2 = [{ other: 1 }, { other: 2 }, { other: 3 }];
console.log(data1.map((a, i) => Object.assign({}, a, { myNewAttribute: data2[i] })));
You don't need lodash:
myArr1.map((e1, idx) => Object.assign({}, e1, {myNewAttribute: myArr2[idx]}))
You could get fancy and write a little function called map2, which takes two arrays, and invokes a callback with the two elements:
function map2(a1, a2, fn) {
return a1.map((elt, idx) => fn(elt, a2[idx]);
}
Now you can write the solution as
map2(myArr1, myArr2, (e1, e2) => Object.assign({}, e1, {myNewAttribute: e2}))
From the perspective of program design, what we are doing here is "separating concerns". The first concern is the abstract operation of looping over two arrays in parallel and doing something with each pair of elements. That is what is represented by map2. The second concern is the specific way you want to combine the elements. That is what is represented by the function we are passing to map2. This could be made clearer and somewhat self-documenting by writing it separately:
function combineObjects(e1, e2) {
return Object.assign({}, e1, {myNewAttribute: e2});
}
map2(myArr1, myArr2, combineObjects);
Of course, in the real world, you'd want to handle the case where the two arrays were of different length, pass an index to the callback as a third parameter for use if necessary, support a third thisArg-type parameter analogous to map, etc.
You can do like this:
var first = [{any:1,some:1},{any:2,some:2},{any:3,some:3}];
var second = [{other:1},{other:2},{other:3}];
for(var i = 0; i < first.length; i++){
if(first[i] && second[i]){
first[i]['mycustomitem'] = second[i];
}
}
console.log(first);
In order to prove, what I did comment 30 minutes ago -
How to merge two dictionaries in javascript -
there is a possible reduce approach ...
... firstly provided as lodash based example ...
var
myArr1 = [
{any: 1, some: 1},
{any: 2, some: 2},
{any: 3, some: 3}
],
myArr2 = [
{other: 1},
{other: 2}
],
mergedObjectList = _.reduce(myArr1, function (collector, item_1, idx) {
var
item_2 = collector[idx],
merger = _.assign({}, item_1, item_2);
// or whatever one wants to do to `merger` with `myNewAttribute`
collector[idx] = merger;
return collector;
}, _.clone(myArr2));
console.log("myArr1 : ", myArr1);
console.log("myArr2 : ", myArr2);
console.log("mergedObjectList : ", mergedObjectList);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
... and secondly as language core only based example ...
var
myArr1 = [
{any: 1, some: 1},
{any: 2, some: 2},
{any: 3, some: 3}
],
myArr2 = [
{other: 1},
{other: 2}
],
mergedObjectList = myArr1.reduce(function (collector, item_1, idx) {
var
item_2 = collector[idx],
merger = Object.assign({}, item_1, item_2);
// or whatever one wants to do to `merger` with `myNewAttribute`
collector[idx] = merger;
return collector;
}, Array.from(myArr2));
console.log("myArr1 : ", myArr1);
console.log("myArr2 : ", myArr2);
console.log("mergedObjectList : ", mergedObjectList);
Try this function:
function mergeDictionary(_dctn1,_dctn2)
{
var newDict = [];
for(var i in _dctn1)
{
newDict[i] = _dctn1[i];
}
for(var j in _dctn2)
{
if(newDict[j] == undefined)
{
newDict[j] = _dctn2[j];
}
else
{
for(var k in _dctn2[j])
{
newDict[j][k] = _dctn2[j][k];
}
}
}
return newDict;
}
var myArr1 = [];
myArr1["1"]={any:1,some:1};
myArr1["2"]={any:2,some:2};
myArr1["3"]={any:3,some:3};
var myArr2 = [];
myArr2["1"]={other:1};
myArr2["2"]={other:2};
console.log(mergeDictionary(myArr1, myArr2));

Categories

Resources