merging two array of objects with custom compare function with javascript - javascript

I have two array of objects like:
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
and:
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
I need to merge this two array in one array and sum the "count" values in returned array when the "title" properties is same:
the last answer must be:
[{title:"name1",count:5},{title:"name2",count:8},{title:"name3",count:5},{title:"name4",count:3},{title:"name5",count:8}]
how can i do this???

You can use Array#forEach and Array#some to achieve a result
var M = A.concat(B)
var C = [];
M.forEach(function(a) {
var index;
if (C.some(function(c, i) { index = i; return a.title == c.title; })) {
C[index].count += a.count;
} else {
C.push(a);
}
});
console.log(C); // as you expect

Solution with Array.concat and Array.map functions:
var merged = A.concat(B), titles = [], result = [];
merged.map(function(obj){
if (titles.indexOf(obj.title) === -1) {
titles.push(obj.title);
result.push(obj);
} else {
result[titles.indexOf(obj.title)]['count'] += obj['count'];
}
});
console.log(result); // will output the expected array of objects

It can be done like this https://jsfiddle.net/menm9xeo/
var noMatch;
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
//for each A, loop through B's. If a match is found combine the Counts in A.
for(var i=0;i<A.length;i++){
for(var j=0;j<B.length;j++){
if(A[i].title == B[j].title){
A[i].count += B[j].count;
}
}
}
//find all B's that were not combined with A in the previous step, and push them into A.
for(var i=0;i<B.length;i++){
noMatch = true;
for(var j=0;j<A.length;j++){
if(B[i].title == A[j].title){
B[i].count += A[j].count;
noMatch = false;
}
}
if(noMatch){A.push(B[i]);}
}

Heres a simple 3 line answer (minus the A/B vars); utilizes the fact that objects must have unique keys
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
var o = {};
A.concat(B).forEach(function(a){o[a.title] = o.hasOwnProperty(a.title)? o[a.title]+a.count: a.count});
var AB = Object.keys(o).map(function(j){ return {title:j,count:o[j]} });

This proposal is merging and counting with a temporary object and Array#forEach()
The forEach() method executes a provided function once per array element.
var arrayA = [{ title: "name1", count: 5 }, { title: "name2", count: 1 }, { title: "name3", count: 3 }],
arrayB = [{ title: "name2", count: 7 }, { title: "name3", count: 2 }, { title: "name4", count: 3 }, { title: "name5", count: 8 }],
result = function (array) {
var o = {}, r = [];
array.forEach(function (a) {
if (!(a.title in o)) {
o[a.title] = { title: a.title, count: 0 };
r.push(o[a.title]);
}
o[a.title].count += a.count;
});
return r;
}(arrayA.concat(arrayB));
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');

Using lodash ._concat function :
var result = _.concat(A, B);
Fiddle

Related

How to remove duplicate names from array of objects

var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
I have tried the following:
function removeDuplicates:(dataObject){
self.dataObjectArr = Object.keys(dataObject).map(function(key){
return dataObject[key];
});
for(var i= 0; i < self.dataObjectArr.length; i++ ){
self.dataObjectArr[i]['name'] = self.dataObjectArr[i];
self.uniqArr = new Array();
for(var key in self.dataObjectArr){
self.uniqArr.push(self.dataObjectArr[key]);
}
}
self.uniqObject = DataMixin.toObject(self.uniqArr);
return self.uniqObject;
}
But I get error saying: Uncaught TypeError: Converting circular structure to JSON.
You should push the name to an array or a set and check the same in the following..
var arr = [{
level: 0,
name: "greg"
}, {
level: 0,
name: "Math"
}, {
level: 0,
name: "greg"
}]
function removeDuplicates(arr) {
var temp = []
return arr.filter(function(el) {
if (temp.indexOf(el.name) < 0) {
temp.push(el.name)
return true
}
})
}
console.log(removeDuplicates(arr))
Here's a generic "uniquify" function:
function uniqBy(a, key) {
var seen = new Set();
return a.filter(item => {
var k = key(item);
return !seen.has(k) && seen.add(k)
});
}
///
var arr = [
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:"joe"},
{level:0,name:Math},
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:Math},
{level:0,name:"greg"}
];
uniq = uniqBy(arr, x => x.name);
console.log(uniq);
See here for the in-depth discussion.
I believe you have a syntax error " removeDuplicates:(dataObject){ ..."
should be without the ":" >> " removeDuplicates(dataObject){ ... "
"
You can try this :
function removeDuplicates(arr){
var match={}, newArr=[];
for(var i in arr){ if(!match[arr[i].name]){ match[arr[i].name]=1; var newArr=i; } }
return newArr;
}
arr = removeDuplicates(arr);
You can use $.unique(), $.map(), $.grep()
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var res = $.map($.unique($.map(arr, el => el.name)), name =>
$.grep(arr, el => el.name === name)[0]);
jsfiddle https://jsfiddle.net/4tex8xhy/3
Or you can use such libraries as underscore or lodash (https://lodash.com/docs/4.16.2). Lodash example:
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var result = _.map(_.keyBy(arr,'name'));
//result will contain
//[
// {
// "level": 0,
// "name": "greg"
// },
// {
// "level": 0,
// "name": "Math"
// }
//]
Ofc. one thing to always consider in these tasks, what do you want exactly are you going to do: modify an existing array, or get a new one back. This example returns you a new array.

JavaScript - find unique values in array of objects, with count and create a new array of objects

I have an array of objects as below:
var arr =[
{
price:20,
rule:a
},
{
price:10,
rule:b
},
{
price:5,
rule:a
},
{
price:50,
rule:b
},
{
price:70,
rule:b
}
]
I want to extract an array of objects out of this as below:
var filteredArr = [
{
rule:a,
countOfRule:2,
minPriceForThisRule:5
},
{
rule:b,
countOfRule:3,
minPriceForThisRule:10
}
]
This means:
1) I want to create new array with no. of objects as unique no. of rules in first array "arr"
2) Need to count the unique rule repetition and add as property in new array objects - given as "countOfRule"
3) Find the minimum price for in a category of unique rule - given as "minPriceForThisRule"
I have read similar answers on SO and was able to get first 2 conditions only, and that too were not in the format as i need.
What I tried, referring to links on SO:
var ff = {},e;
for (var i = 0,l=arr.length; i < l; i++) {
e = arr[i];
ff[e.rule] = (ff[e.rule] || 0) + 1;
}
But this gives only a single object as
{
a : 2,
b: 3
}
You can do this with forEach and thisArg optional parameter.
var arr = [{"price":20,"rule":"a"},{"price":10,"rule":"b"},{"price":5,"rule":"a"},{"price":50,"rule":"b"},{"price":70,"rule":"b"}],
r = [];
arr.forEach(function(e) {
if(!this[e.rule]) {
this[e.rule] = {rule: e.rule, countOfRule: 0, minPriceForThisRule: e.price}
r.push(this[e.rule]);
}
this[e.rule].countOfRule++;
if(this[e.rule].minPriceForThisRule > e.price) this[e.rule].minPriceForThisRule = e.price;
}, {});
console.log(r)
I would use reduce. Something like:
var reduced = arr.reduce(function(memo, obj){
var rule = memo[obj.rule];
rule.countOfRule++;
if(!rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
} else{
if(obj.price < rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
}
}
return memo;
}, map);
where the initial map looks like:
var map = {
1: {
rule: 1,
countOfRule: 0,
minPriceForThisRule: null
},
2: {
rule: 2,
countOfRule: 0,
minPriceForThisRule: null
}
}
Of course you could dynamically create the map if needed.
https://plnkr.co/edit/wLw3tEx2SMXmYE7yOEHg?p=preview
This is how i would do this job,
var arr =[
{
price:20,
rule:"a"
},
{
price:10,
rule:"b"
},
{
price:5,
rule:"a"
},
{
price:50,
rule:"b"
},
{
price:70,
rule:"b"
}
],
reduced = arr.reduce((p,c) => { var fo = p.find(f => f.rule == c.rule);
fo ? (fo.countOfRule++,
fo.minPriceForThisRule > c.price && (fo.minPriceForThisRule = c.price))
: p.push({rule:c.rule, countOfRule:1, minPriceForThisRule: c.price});
return p;
},[]);
console.log(reduced);
Arrows might not work at IE or Safari. If that would be a problem please replace them with their conventional counterparts.

Finding the index of an object within an array efficiently

I am trying to find the index of an object within an array. I know there is a way to do this with underscore.js but I am trying to find an efficient way without underscore.js. Here is what I have :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var rx = /\{.*?\}/; // regex: finds string that starts with { and ends with }
var arr = []; // creates new array
var str = JSON.stringify(arrayOfObjs); // turns array of objects into a string
for (i = 0; i < arrayOfObjs.length; i++) { // loops through array of objects
arr.push(str.match(rx)[0]); // pushes matched string into new array
str = str.replace(rx, ''); // removes matched string from str
}
var Index = arr.indexOf(JSON.stringify(key)); // stringfy key and finds index of key in the new array
alert(Index);
}
FindIndex({"ob2": "test1"});
JSFIDDLE
This works but I am afraid it isn't very efficient. Any alternatives?
Here's one way to do it, somewhat reliably and a little more efficiently, using some() and stopping as soon as the objects don't match etc.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var index = -1;
arrayOfObjs.some(function(item, i) {
var result = Object.keys(key).some(function(oKey) {
return (oKey in item && item[oKey] === key[oKey]);
});
if (result) index = i;
return result;
});
return index;
}
var index = FindIndex({"ob2": "test1"});
document.body.innerHTML = "'{\"ob2\": \"test1\"}' is at index : " + index;
A hash table with an example of access.
var arrayOfObjs = [{ "obj1": "test1" }, { "obj2": "test1" }, { "obj1": "test3" }],
hash = {};
arrayOfObjs.forEach(function (a, i) {
Object.keys(a).forEach(function (k) {
hash[k] = hash[k] || {};
hash[k][a[k]] = i;
});
});
document.write('<pre>' + JSON.stringify(hash['obj2']['test1'], 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(hash, 0, 4) + '</pre>');
One way of doing this would be to use every to see if each key in the "filter" has a matching, correct value in an object. every ensures that the loop stops as soon as it finds a mismatched or missing value.
function log(msg) {
document.querySelector('pre').innerHTML += msg + '\n';
}
var arr = [
{
a: 1
},
{
b: 2
},
{
c: 3,
d: 4
},
{
a: 1 // Will never reach this since it finds the first occurrence
}
];
function getIndex(filter) {
var keys = Object.keys(filter);
for (var i = 0, len = arr.length; i < len; i++) {
var obj = arr[i];
var match = keys.every(function(key) {
return filter[key] === obj[key];
});
if (match) {
return i;
}
}
return -1;
}
log(getIndex({ a: 1 }));
log(getIndex({ b: 2 }));
log(getIndex({ c: 3 }));
log(getIndex({ c: 3, d: 4 }));
log(getIndex({ e: 5 })); // Doesn't exist, won't find it
<pre></pre>
For an alternative to your customly built approach, lodash's findIndex method does exactly this for you:
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
_.findIndex(arrayOfObjs, {"ob2": "test1"}); // => 1
Since testing equality on two different objects will always return false you could first test keys and then values ,
using reduce :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1" , k2:2
}, {
"ob1": "test3"
}];
function getI( obj, arr){
const checkK= Object.keys(obj);
return arr.reduce((ac,x,i) => {
if ( checkK.every(z => x[z] && obj[z] === x[z]) )
ac.push(i);
return ac;
},[])
}
document.write( 'result is :'+ getI({ob2:'test1', k2:2},arrayOfObjs))
findIndex won't work in old browsers, but was designed for this specific purpose.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
return arrayOfObjs.findIndex(
obj => Object.keys(key).every(name => key[name] === obj[name])
);
}
alert(FindIndex({"ob2": "test1"})); // 1

How to check if array is unique on specific object property?

I have an array of objects:
var array1 = [
{
property1: 10,
property2: "abc"
},
{
property1: 11,
property2: "def"
},
{
property1: 10,
property2: "ghi"
}
];
Now what I want is this array will be said not unique as per value of property1.
This means that this array contains 2 elements with property1=10, so the array does not contain unique value of property1.
To check this, I can use a for loop:
for (var i = 0; i < array1.length; i++) {
var array2 = array1.slice(); // copy array
array2.remove(array1[i]);
var temppropety1 = array1[i].property1;
for (var j = 0; j < array2.length; j++) {
if (array2[J].property1==temppropety1) {
return true;
}
}
}
But is there an easier way or a library to find this?
Here is a straightforward way to test for uniqueness on property1. Loop through the objects in the outer array and add each object's property1 to a temp array if it is not already in that temp array. If a duplicate value is encountered, return false meaning property1 is not unique.
function isUnique(arr) {
var tmpArr = [];
for(var obj in arr) {
if(tmpArr.indexOf(arr[obj].property1) < 0){
tmpArr.push(arr[obj].property1);
} else {
return false; // Duplicate value for property1 found
}
}
return true; // No duplicate values found for property1
}
Demo: http://jsbin.com/lohiqihipe/1/
First, you could reduce (aggregate) the objects by grouping them by the value of property1:
var grouped = array.reduce(function(grouped, item) {
var propertyValue = item.property1;
grouped[propertyValue] = (grouped[propertyValue] || 0) + 1;
return grouped;
}, {});
Then you check that every key of the resulting object has a value of 1:
var result = Object.keys(grouped).every(function(key) {
return grouped[key] === 1;
});
I suggest that array can be quite big so I'd prefer not to copy it and just validate properties.
Also it is not an option to use map function of array because in this case you won't be able to break a cycle on first match:
var equals = function(array) {
var co = {};
var unique = true;
for(var i = 0; i < array.length; i++) {
var o = array[i];
if (co[o.property1]) {
unique = false;
break;
} else {
co[o.property1] = true;
}
}
return unique;
};
You can convert your array to flat structure:
array1.map(function(item) { return item.property1; });
and now your problem simplify to check duplicates in simple array
var array1 = ["a","b","b","c","d","e","f"];
var uniqueItems = [];
$.each(array1, function(i, el){
if($.inArray(el, uniqueItems) === -1) uniqueItems.push(el);
});
References:
https://stackoverflow.com/a/840808/4772988
https://stackoverflow.com/a/9229932/4772988
You can use a couple of helpers to abstract it:
var uniqBy = function(f, xs) {
var seen = []
return xs.filter(function(x) {
var fx = f(x)
if (seen.indexOf(fx) > -1) return
seen.push(fx)
return true
})
}
var dot = function(k) {
return function(obj) {
return obj[k]
}
}
Then filter out duplicates by the property, and compare the length of the result to the original array. If they don't match, then they must not be unique:
var res = uniqBy(dot('property1'), array1)
var isUnique = array1.length === res.length
console.log(isUnique) // false
If you got only numbers or only strings to remove duplicates from, then you can improve performance by using an object instead of an array to keep track of elements seen so far.
You can use lodash library to achieve this.
Here is the library documentation: https://lodash.com/docs/4.17.5#filter
Method:-
function isDuplicatesPresent(list, propertyName){
return _.filter(list, function (value) {
return _.filter(list, function(innerValue){ reutrn innerValue[propertyName] === value[propertyName]}).length > 1;
}).length > 0;
}
Example:-
var users = [
{ user: 'barney', age: 36, active: true },
{ user: 'fred', age: 40, active: false },
{ user: 'barney', age: 37, active: true}
];
let duplicates = _.filter(users, function (value) {
return _.filter(users, {user:value.user}).length > 1;
});
Result:
console.log(duplicates)
> [
{"user": "barney","age": 36,"active": true},
{"user": "barney","age": 37,"active": true}
];

Need an algorithm to manipulate array structure in javascript

In javascript, here is my start array:
[{
name: 'aaa',
value: 1
},
{
name: 'bbb',
value: 0
},
{
name: 'bbb',
value: 1
}]
I want to transform it into this array as result:
[{
name: 'aaa',
value: 1
},
{
name: 'bbb',
value: [0, 1]
}]
I need a good and simple algorithm to do this
How about:
var array = [{
name: 'aaa',
value: 1
},
{
name: 'bbb',
value: 0
},
{
name: 'bbb',
value: 1
}];
var map = {};
for(var i = 0; i < array.length; i++) {
var name = array[i].name;
if (map[name] === undefined) {
map[name] = [];
}
map[name].push(array[i].value);
}
var result = [];
for(var key in map) {
var value = map[key];
result.push({
name: key,
value: value.length === 1 ? value[0] : value
});
}
Easiest way is to create a map to keep track of which names are used. Then convert this map back to an array of objects.
If you want to use Arrays for value then change it to:
result.push({
name: key,
value: value
});
here's pseudocode for simplest implementation
hash = {}
for(pair in array) {
hash[pair.name] ||= []
hash[pair.name] << pair.value
}
result = []
for(k, v in hash) {
result << {name: k, value: v}
}
This function does the trick
function consolidate(var arrayOfObjects)
{
// create a dictionary of values first
var dict = {};
for(var i = 0; i < arrayOfObjects.length; i++)
{
var n = arrayOfObjects[i].name;
if (!dict[n])
{
dict[n] = [];
}
dict[n].push(arrayOfObjects[i].value);
}
// convert dictionary to array again
var result = [];
for(var key in dict)
{
result.push({
name: key,
value: dict[key].length == 1 ? dict[key][0] : dict[key]
});
}
return result;
}
An alternative solution:
function convert(arr) {
var res = [];
var map = {};
for (var i=0;i<arr.length;i++) {
var arrObj = arr[i];
var oldObj = map[arrObj.name];
if (oldObj == undefined) {
oldObj = {name:arrObj.name, value:arrObj.value};
map[arrObj.name] = oldObj;
res.push(oldObj);
} else {
if( typeof oldObj.value === 'number' ) {
oldObj.value = [oldObj.value];
}
oldObj.value.push(arrObj.value);
}
}
return res;
}
In theory it should work a bit faster and use less memory. Basically it creates a result array and a map which is an index for the same array (no duplicate objects). So it fills the result in one iteration instead of two and does not need to convert map to array (which saves several CPU cycles :P ).
Added:
Here is a variation of that function in case value: [1] is acceptable:
function convert(arr) {
var res = [];
var map = {};
for (var i=0;i<arr.length;i++) {
var arrObj = arr[i];
var oldObj = map[arrObj.name];
if (oldObj == undefined) {
oldObj = {name:arrObj.name, value:[arrObj.value]};
map[arrObj.name] = oldObj;
res.push(oldObj);
} else {
oldObj.value.push(arrObj.value);
}
}
return res;
}

Categories

Resources