Related
I have two arrays like this
let array1 = [{
'id': 1,
'name': 'A'
}, {
'id': 2,
'name': 'B'
}, {
'id': 3,
'name': 'C'
}]
let array2 = [{
'id': 1,
'name': 'x'
}, {
'id': 2,
'name': 'y'
}]
I want to update array 1 with the array 2 object values which are matching based on id values.
Result would be something like this.
[{
'id': 1,
'name': 'x'
}, {
'id': 2,
'name': 'y'
}, {
'id': 3,
'name': 'C'
}]
I have written something like this but not working .
array1.forEach(item1 => {
const itemFromArr2 = array2.find(item2 => item2.id== item1.id);
if (itemFromArr2) {
item1= itemFromArr2;
}
}
)
Please suggest me how to do this.
You might want to check this one-liner out:
array1.map(e => (e.name = array2.find(a => a.id == e.id)?.name || e.name, e));
Explanation: We are mapping over array1 and searching for the matching id in array2, if it is found (array2.find(a => a.id == e.id)?.name), we override the name property (e.name = ...), otherwise we keep it as it is (... || e.name).
Small example:
let array1 = [{
'id': 1,
'name': 'A'
}, {
'id': 2,
'name': 'B'
}, {
'id': 3,
'name': 'C'
}]
let array2 = [{
'id': 1,
'name': 'x'
}, {
'id': 2,
'name': 'y'
}]
const newarray = array1.map(e => (e.name = array2.find(a => a.id == e.id)?.name || e.name, e));
console.log(newarray);
Edit according to #Roster's comment, if you want to override the whole entry use this line:
array1.map(e => (e = array2.find(a => a.id == e.id) || e, e));
Second example:
let array1 = [{
'id': 1,
'name': 'A'
}, {
'id': 2,
'name': 'B'
}, {
'id': 3,
'name': 'C'
}]
let array2 = [{
'id': 1,
'name': 'x'
}, {
'id': 2,
'name': 'y',
'otherproperty': 42
}]
const newarray = array1.map(e => (e = array2.find(a => a.id == e.id) || e, e));
console.log(newarray);
Using a hashmap to update the array.
The reason of hashmap is for performance.
const array1 = [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C" },
];
const array2 = [
{ id: 1, name: "x" },
{ id: 2, name: "y" },
];
const hashMap2 = array2.reduce((carry, item) => {
const { id } = item;
if (!carry[id]) {
carry[id] = item;
}
return carry;
}, {});
const output = array1.map(item => {
const newName = hashMap2[item.id]?.name;
if (newName) {
item.name = newName;
}
return item;
});
console.log(output);
Here's a solution using Generics:
const array1 = [{
'id': 1,
'name': 'A'
}, {
'id': 2,
'name': 'B'
}, {
'id': 3,
'name': 'C'
}]
const array2 = [{
'id': 1,
'name': 'x'
}, {
'id': 2,
'name': 'y'
}]
function mergeArray<T>(arr1: T[], arr2: T[], identifier: keyof T): T[] {
for(const oItem of arr2){
const itemInArr1 =
arr1.find(item => item[identifier] === oItem[identifier]);
if(itemInArr1){
for(const key in itemInArr1){
itemInArr1[key] = oItem[key];
}
} else {
arr1.push(oItem);
}
}
return arr1;
}
console.log(mergeArray(array1,array2, 'id'));
Playground
This iterates over the itmes in array2and checks their existence inside array1 based on the identifier.
Based on whether or not the item exists in array1, the item will be modified, or the item from array2 is pushed into array1.
Convert the update array to a Map, then iterate the target array with Array.map(), and merge it with the object of the same key in the upateMap if it exists:
const fn = (predicate, target, update) => {
const updateMap = new Map(update.map(o => [predicate(o), o]))
return target.map(o => {
const key = predicate(o)
return updateMap.has(key)
? { ...o, ...updateMap.get(key)}
: o
})
}
const array1 = [{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}]
const array2 = [{"id":1,"name":"x"},{"id":2,"name":"y"}]
const result = fn(o => o.id, array1, array2);
console.log(result);
With types (TS playground):
const fn = <T>(predicate: (arg: T) => any, target: T[], update: T[]) => {
const updateMap = new Map(update.map(o => [predicate(o), o]))
return target.map(o => {
const key = predicate(o)
return updateMap.has(key)
? { ...o, ...updateMap.get(key)}
: o
})
}
Suppose an array of objects:
arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
]
How can I randomly choose two Item of catId=1 and each Items from remaining category.
Required
arr2 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 3, 'name': 'G' }
]
This is a very simple and naïve approach like I explained on my comment, but it gets the job done:
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
//gets items with catId = 1, and shuffles them
var cat1 = shuffle(arr1.filter(function(item) {
return item.catId == 1;
}));
var otherCat = [];
//pushes items in the otherCat array that aren't catId = 1, and not duplicate category
for (var i = 0; i < arr1.length; i++) {
//if not catId = 1 and not already in array
if (arr1[i].catId != 1 && !find(arr1[i])) {
//get all items in this category, and shuffles them to get a random item
var thisCat = shuffle(arr1.filter(function(item) { return item.catId == arr1[i].catId; }))[0];
otherCat.push(thisCat);
}
}
//finds an item in otherCat array by catId
function find(item) {
return otherCat.find(function(i) {
return item.catId === i.catId;
});
}
var result = [];
result.push(cat1[0]);
result.push(cat1[1]);
//concatenate both arrays
Array.prototype.push.apply(result, otherCat);
console.log(JSON.stringify(result));
I coded it this way because it is very simple to see. You could in theory loop through the whole array once to get all catId = 1 and other catId into 2 different arrays (I know I am doing multiple passes to the array, but like I said, this is just so you can get the idea).
Another way of doing it (perhaps a little more complex) is by grouping the items by category, then looping thru each category and grabbing a random element (2 in the case of catId == 1):
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//groups by a property
//https://stackoverflow.com/a/34890276/752527
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
var result = [];
var grouped = groupBy(arr1, 'catId');
var keys = Object.keys(grouped);
for (var i = 0; i < keys.length; i++) {
var group = grouped[keys[i]]
//if i have items in my group, shuffle them and grab 1, or 2 items from it
if (group && group.length > 0) {
var cat = shuffle(group);
result.push(cat[0]);
//adds the second item with catId ==1
if (group[0].catId === 1) {
result.push(cat[1]);
}
}
}
console.log(JSON.stringify(result));
If you want to return a list of n-number of items from a particular category and one from the remaining categories, then you could group the items by their catId and then map the entries to a randomized count (length) based on whether the current key is the chosen bias.
Edit: I added a bias to keep n-number of items from the category of choice.
const categories = [
{ 'catId': 1, 'name': 'A' }, { 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' }, { 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' }, { 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' }, { 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' }, { 'catId': 1, 'name': 'J' }
];
const sortFn = (
{ catId: ai, name: an },
{ catId: bi, name: bn }
) =>
(ai - bi) || an.localeCompare(bn);
const main = () => {
print(pickRand(categories, ({ catId }) => catId, 1, 2).sort(sortFn));
};
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
const grouped = (arr, keyFn) => arr.reduce((acc, item, idx) =>
({ ...acc, [keyFn(item)]: [...(acc[keyFn(item)] ?? []), idx] }), {});
const pickRand = (arr, keyFn, bias, count) =>
Object
.entries(grouped(arr, keyFn))
.flatMap(([key, idx]) =>
shuffle(idx).slice(0, bias == key ? count : 1)
.map(i => arr[i]));
const print = (arr) => console.log(arr.map(x => JSON.stringify(x)).join('\n'));
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
I have array of objects in object have different key so i want the object having minimum value in that array
list = [{
'id': 4,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 1,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 2,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 3,
'name': 'nitin',
'group': 'angularjs'
}
]
I tried by
var minValue = Math.min.apply(Math, list.map(function (o) {
return o.id;
}))
but it returns only the id not whole object then i have to make more filter for get object
is there any direct method to find object?
You can use Array reduce method:
var list = [
{
'id':4,
'name':'nitin',
'group':'angularjs'
},
{
'id':1,
'name':'nitin',
'group':'angularjs'
},
{
'id':2,
'name':'nitin',
'group':'angularjs'
},
{
'id':3,
'name':'nitin',
'group':'angularjs'
}];
var result = list.reduce(function(res, obj) {
return (obj.id < res.id) ? obj : res;
});
console.log(result);
Using Array#reduce()
list = [{
'id': 4,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 1,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 2,
'name': 'nitin',
'group': 'angularjs'
},
{
'id': 3,
'name': 'nitin',
'group': 'angularjs'
}
];
let min = list.reduce((prev, curr) => prev.id < curr.id ? prev : curr);
console.log(min);
I tried these solutions with Array.reduce but none of them were full proof. They do not handle cases where the array is empty or only has 1 element.
Instead this worked for me:
const result = array.reduce(
(prev, current) =>
(prev?.id ?? current?.id) >= current?.id ? current : prev,
null,
);
It returns null if array is empty and handles other cases well.
You can check for the object with the lowest id by looping through the array like so, probably not the neatest way, but a solution either way:
var list = [
{
'id':4,
'name':'nitin',
'group':'angularjs'
},
{
'id':1,
'name':'nitin',
'group':'angularjs'
},
{
'id':2,
'name':'nitin',
'group':'angularjs'
},
{
'id':3,
'name':'nitin',
'group':'angularjs'
}]
var tmp = list[0].id;
list.forEach(function (entry) {
if (entry.id < tmp) {
tmp = entry;
}
});
console.log(tmp);
https://jsfiddle.net/camgssk3/
other option may be to filter your array as you already have min "id"
var list = [
{
'id':4,
'name':'nitin',
'group':'angularjs'
},
{
'id':1,
'name':'nitin',
'group':'angularjs'
},
{
'id':2,
'name':'nitin',
'group':'angularjs'
},
{
'id':3,
'name':'nitin',
'group':'angularjs'
}];
var min = Math.min.apply(Math, list.map(function (o) {
return o.id;
}));
filtered = list.filter(function(elem){
if(elem.id == min){console.log(elem);return elem;}
})
I made a function in order to find an element in a tree object. My function works, but sometimes the function don't find the value and stop before looking all the tree.
I can't explain why it works sometimes and sometimes not.
Here is my fiddle : http://jsfiddle.net/3cdwA/2/
When you click on categories like "Sciences" you can see that it works. But if you click on "Bandes-Dessinées" it should display "Comics" but it doesn't.
Here is my recursive function :
function getChildrenFromCurrentFolder(tree, targetFolder) {
console.log(tree);
// Find recursivly all direct children of targeted folder
if (targetFolder == tree.id) {
return tree.folders;
} else if (tree.folders.length > 0) {
var folders = [];
for (i = 0; folders.length == 0 && i < tree.folders.length; i++) {
folders = getChildrenFromCurrentFolder(tree.folders[i], targetFolder);
}
return folders;
}
return [];
}
here is my test tree :
tree = {
'id': 1,
'name': 'Mes Bookmarks',
'folders': [
{
'id': 2,
'name': 'Sciences',
'folders': [
{
'id': 3,
'name': 'Biologie',
'folders': [
{
'id': 12,
'name': 'Neurologie',
'folders': []
}
]
},
{
'id': 4,
'name': 'Astrophysique',
'folders': [
{
'id': 8,
'name': 'Cosmologie',
'folders': [
{
'id': 10,
'name': 'Système solaire',
'folders': []
}
]
},
{
'id': 9,
'name': 'Trous noirs',
'folders': []
}
]
},
{
'id': 5,
'name': 'Mathématiques',
'folders': []
}
]
},
{
'id': 6,
'name': 'Actualités',
'folders': [
{
'id': 11,
'name': 'Monde',
'folders': []
}
]
},
{
'id': 7,
'name': 'Bandes-dessinées',
'folders': [
{
'id': 13,
'name': 'Comics',
'folders': []
}
]
}
]
};
It's a simple, but common mistake. You forgot to declare your loop variables and that's trouble when doing recursion as you're creating a global that's being reused:
function displayFolders() {
...
for (var i = folders.length-1; i >= 0; i--)
... --^--
}
function getChildrenFromCurrentFolder(tree, targetFolder) {
...
for (var i = 0; folders.length == 0 && i < tree.folders.length; i++) {
... --^--
}
function getBreadcrumb(tree, targetFolder, breadcrumb) {
...
for (var i = 0; i < tree['folders'].length; i++)
... --^--
}
I'm not sure all the other logic is correct, but this definitely changes the behavior.
http://jsfiddle.net/3cdwA/4/
_.intersection([], [])
only works with primitive types, right?
It doesn't work with objects. How can I make it work with objects (maybe by checking the "Id" field)?
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]
In this example, the result should be:
_.intersection(a, b);
[ {'id': 1, 'name': 'jake' } ];
You can create another function based on underscore's function. You only have to change one line of code from the original function:
_.intersectionObjects = function(array) {
var slice = Array.prototype.slice; // added this line as a utility
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
//return _.indexOf(other, item) >= 0;
return _.any(other, function(element) { return _.isEqual(element, item); });
});
});
};
In this case you'd now be using underscore's isEqual() method instead of JavaScript's equality comparer. I tried it with your example and it worked. Here is an excerpt from underscore's documentation regarding the isEqual function:
_.isEqual(object, other)
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal.
You can find the documentation here: http://documentcloud.github.com/underscore/#isEqual
I put up the code on jsFiddle so you can test and confirm it: http://jsfiddle.net/luisperezphd/jrJxT/
Here is an alternative algorithm that should be flexible and perform better. One of those improvements is that you can specify your own comparison function so in your case you can just compare the id if it's a unique identifier.
function intersectionObjects2(a, b, areEqualFunction) {
var results = [];
for(var i = 0; i < a.length; i++) {
var aElement = a[i];
var existsInB = _.any(b, function(bElement) { return areEqualFunction(bElement, aElement); });
if(existsInB) {
results.push(aElement);
}
}
return results;
}
function intersectionObjects() {
var results = arguments[0];
var lastArgument = arguments[arguments.length - 1];
var arrayCount = arguments.length;
var areEqualFunction = _.isEqual;
if(typeof lastArgument === "function") {
areEqualFunction = lastArgument;
arrayCount--;
}
for(var i = 1; i < arrayCount ; i++) {
var array = arguments[i];
results = intersectionObjects2(results, array, areEqualFunction);
if(results.length === 0) break;
}
return results;
}
You can use it like this:
var a = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'} ];
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ];
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ];
var result = intersectionObjects(a, b, c, function(item1, item2) {
return item1.id === item2.id;
});
Or you can leave out the function and it will use underscores _.isEqual() function, like so:
var result = intersectionObjects(a, b, c);
You can find it on jsFiddle here: http://jsfiddle.net/luisperezphd/43vksdn6/
The array methods in underscore are very powerful, you should only need a few lines to accomplish what you want to do:
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
var result = _(a).chain().map(function(ea) {
return _.find(b, function(eb) {return ea.id == eb.id;});
}).compact().value();
If you have large arrays you can get rid of the compact() call with one additional line:
var result = [];
_.each(a, function(ea) {
var entry = _.find(b, function(eb) {return ea.id == eb.id;});
if (entry) result.push(entry);
});
I'd like to share my general solution for those cases.
I added a general function to underscore, using mixin, which performs a binary 'array' operation on two collections, according to a given Hash function:
_.mixin({
collectionOperation: function(arr1, arr2, hash, action) {
var iArr1 = _(arr1).indexBy(hash)
, iArr2 = _(arr2).indexBy(hash);
return action(_(iArr1).keys(), _(iArr2).keys()).map(function (id) {
return iArr1[id] || iArr2[id];
});
}
});
Usage example:
_([{id:1,v:'q'},{id:2,v:'p'}]).collectionOperation([{id:3,v:'pq'}], 'id', _.union )
Note that 'id' may be replaced with a function.
I believe this solution is O(n+m).
In lodash 4.0.0. We can try like this
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
_.intersectionBy(a, b, 'id');
Output:
[ {'id': 1, 'name': 'jake' } ];
Technically, it does work on objects, but you need to be careful of reference equality.
var jake = {'id': 1, 'name': 'jake' },
jenny = {'id':4, 'name': 'jenny'},
nick = {'id': 9, 'name': 'nick'};
var a = [jake, jenny]
var b = [jake, nick];
_.intersection(a, b);
// is
[jake]
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
Working function:
function intersection(a,b){
var c=[];
for(m in a){
for(n in b){
if((a[m].id==a[n].id)&&(a[m].name==b[n].name))
c.push(a[m]);
}}
return c;
}
console.log(intersection(a,b));
I have also tried code in jQuery specially after Pointy's suggestion. Compare has to be customizable as per the structure of JSON object.
<script type="text/javascript">
jQuery(document).ready(function(){
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ];
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ];
var c=[];
jQuery.each(a, function(ka,va) {
jQuery.each(b, function(kb,vb) {
if(compare(va,vb))
c.push(va);
});
});
console.log(c);
});
function compare(a,b){
if(a.id==b.id&&a.name==b.name)
return true;
else return false;
}
</script>
If you wanna compare only objects:
b = {"1":{"prod":"fibaro"},"2":{"prod":"aeotec"},"3":{"prod":"sw"}};
a = {"1":{"prod":"fibaro"}};
_.intersectObjects = function(a,b){
var m = Object.keys(a).length;
var n = Object.keys(b).length;
var output;
if (m > n) output = _.clone(a); else output = _.clone(b);
var keys = _.xor(_.keys(a),_.keys(b));
for(k in keys){
console.log(k);
delete output[keys[k]];
}
return output;
}
_.intersectObjects(a,b); // this returns { '1': { prod: 'fibaro' } }
//nested array is in the format of [[],[],[]]
function objectArrayIntersection(nestedArrays){
let intersectingItems = [];
let uniqArr = _.uniq(_.flatten(nestedArrays)); //intersecting items removed
const countOfNestedArrays = nestedArrays.length;
for (let index = 0; index < uniqArr.length; index++) {
let uniqItem = uniqArr[index];
let foundCount = 0;
for(var j = 0;j<countOfNestedArrays;j++){
var i = _.indexOf(nestedArrays[j],uniqItem);
if(i != -1)
foundCount ++;
}
if(foundCount == countOfNestedArrays){
intersectingItems.push(uniqItem);
}
}
return intersectingItems;
}
I tried solving it this way.
var a = {a:'a1',b:'b1'},
b = {a:'a2',b:'b2',c:'c2'};
_.pick(a,_.intersection(_.keys(a),_.keys(b)));
// {a:'a1',b:'b1'}