I have a stupid problem that at first seems to be simple to solve, but turns out to be tricky.
I have an array of objects, each with two properties: id and value:
[
{id: 2, value: 10},
{id: 4, value: 3},
{id: 2, value: 2},
{id: 1, value: 15}
]
I want to write an algorithm that sums up the values of ones with similar id.
My end result should be a new array with only the merged objects:
[
{id: 2, value: 12},
{id: 4, value: 3},
{id: 1, value: 15}
]
I've tried the following, but it doesn't work:
var arr = [];
arr.push({id: 2, visit:10});
arr.push({id: 4, visit:3});
arr.push({id: 2, visit:2});
arr.push({id: 1, visit:15});
// Deep copy
var copy = jQuery.extend(true, [], arr);
var masterArr = [];
for (var i = 0; i < arr.length; i++) {
var objArr = [];
objArr.push(arr[i]);
for (var j = copy.length-1; j > -1; j--) {
if (arr[i].id === copy[j].id) {
var q = copy.splice(j,1);
}
}
masterArr.push(objArr);
}
My plan was to first gather all similar objects in separate arrays (objArr), sum them up and put them in an end array (masterArr). I use jquerys extend to make a deep copy (not a reference) and reverse iteration and splice to remove objects thats already been found as "duplicates".
This doesn't work! And it doesn't seem to be a very efficient mehtod to solve my problem.
How could I do this? Performance isn't top priority but rather "nice to have"!
Thanks!
You can do it like this:
// Assuming:
a = [{id: 2, value: 10}, {id: 4, value: 3}, {id: 2, value: 2}, {id: 1, value: 15}]
var b = {}, // Temporary variable;
c = []; // This will contain the result;
// Build a id:value object ( {1: 15, 2: 12, 4: 3} )
a.map(function(current){b[current.id] = (b[current.id] || 0) + current.value});
for(var key in b){ // Form that into the desired output format.
c.push({id: parseInt(key, 10), value: b[key]});
}
console.log(c);
/* [{id: 1, value: 15},
{id: 2, value: 12},
{id: 4, value: 3}] */
I'm using parseInt(key, 10), since the keys are strings, you'll probably want them converted to integers again.
// First group the data based on id and sum the values
var temp = data.reduce(function(result, current) {
result[current.id] = (result[current.id] || 0) + current.value;
return result;
}, {});
// then recreate the objects with proper id and value properties
var result = [];
for (var key in temp) {
result.push({
id: parseInt(key, 10),
value: temp[key]
});
}
console.log(result);
Output
[ { id: 1, value: 15 },
{ id: 2, value: 12 },
{ id: 4, value: 3 } ]
The quickest approach loops over the array only once using Array.prototype.filter():
var tmp = {},
result = arr.filter(function (el) {
if (tmp.hasOwnProperty(el.id)) {
tmp[el.id].visit += el.visit;
return false;
}
else {
tmp[el.id] = el;
return true;
}
});
It also reuses the objects, though this renders the original array to contain inaccurate values. If this is a problem, you can modify the example to copy each object property to a new object.
Related
I am facing problem to merge the two arrays. I have two arrays of objects first is prev having old values and another with updated values. I would like to have result array with all the objects of prev array with its updated value in array next, and also have objects in next array.
Example:
var prev = [{id: 1, val: 'abc'}, {id: 2, val: 'pqr'}];
var next = [{id: 1, val: 'nextVal'}, {id: 3, val: 'xyz'}];
expected
mergeOutput = [
{id: 1, val: 'nextVal'}, // value is updated
{id: 2, val: 'pqr'},
{id: 3, val: 'xyz'}
]
Note: Array order do not matter.
You can use Map() to merge array.
var prev = [{id: 1, val: 'abc'}, {id: 2, val: 'pqr'}];
var next = [{id: 1, val: 'nextVal'}, {id: 3, val: 'xyz'}];
var hash = new Map();
prev.concat(next).forEach(function(obj) {
hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var mergedArray = Array.from(hash.values());
console.log(mergedArray);
Source : StackOverflow
a = [
{ id: 1, books: [1, 2, 3, 4] },
{ id: 2, books: [1, 2, 3] },
{ id: 3, books: [1, 2, 3, 4, 5] },
{ id: 9, books: [1, 2] }
];
b = [{ id: 2, books: [1, 2, 3] }, { id: 3, books: [1, 2, 3, 4, 5] }];
I want to delete array b the same id elements from a.
How to do that? Thanks.
It means:
I want to get the a = [{id: 1, books: [1,2,3,4]}], get rid the same element within array b;
my code is:
const delDuplicate = (a, b) => {
const bLen = b.length;
if (!bLen) return a;
for (let i = 0; i < a.length; i++) {
for (let j = 0; j < bLen; j++) {
if (a[i].id === b[j].id) {
const delItem = a.splice(i, 1)[0];
console.log(delItem);
}
}
}
return a;
};
a = delDuplicate(a, b);
It works, is there a better way? I think reduce and map maybe work too.
These two arrays are not simple array. So can not use a.indexOf(b[i]) !== -1.
You can use .map(), .reduce(), .filter(), .indexOf()
var ids = b.map(o => o.id);
a = a.reduce((res, o) =>
[...res] = [...res.filter(Boolean), ids.indexOf(o.id) < 0 && o], []);
var a = [{id: 1, books: [1,2,3,4]}, {id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}, {id: 9, books: [1,2]}];
var b = [{id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}];
var ids = b.map(o => o.id);
a = a.reduce((res, o) =>
[...res] = [...res.filter(Boolean), ids.indexOf(o.id) < 0 && o], []);
console.log(a);
I found another way to do this. By looping through both arrays and if a match/duplicate is NOT found add this element to a third array. The third array can over write the A array at the end if required. Have tested and works.
var a = [{id: 1, books: [1,2,3,4]}, {id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}, {id: 9, books: [1,2]}];
var b = [{id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}];
var c = []; // New array to sort parsed A array
var boolMatch; // Boolean if duplicate is found
for(i = 0; i < a.length; i++){
boolMatch = false; // Required to reset the Boolean at the start of each loop
for(j = 0; j < b.length; j++){
if(a[i].id == b[j].id){
boolMatch = true;
break;
}
}
if(!boolMatch) c.push(a[i]); // Add to C array if element from A is NOT found in B array
}
Firstly: your question is a bit unclear to me. If you clarify, I can make this answer better. I'm asuming that you are trying to remove elements from b that have same value as the corresponding element in array a. I'm also assuming that you want to update the incidences during the search.
Now IDK the exact syntax of Javascript, so this may be a bit off. However, it should give you a general idea of what to do. (I'll try and fix the code after I research a bit)
a = [{id: 1, books: [1,2,3,4]}, {id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}, {id: 9, books: [1,2]}]
b = [{id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}]
//set loop size to size of the smaller array
var lsize=b.length;
if(a.length<b.length) lsize = a.length;
//loop through length
for(var i = 0; i < lsize; i++) {
if(a[i] != b[i]) { //check if the values of the elements are the same
b.splice(i, 1); //remove the element from b
i=-1; //reset loop to check rest of elements
lsize-=1; //reduce size since there is one less
}
}
To make sure you compare all the elements correctly, you should sort them by the ids to make sure you're comparing the right array elements together. This solution assumes the objects only have one other property to compare, books, which is a sorted array.
// initialize arrays
var a = [{id: 1, books: [1,2,3,4]}, {id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}, {id: 9, books: [1,2]}];
var b = [{id: 2, books: [1,2,3]}, {id: 3, books: [1,2,3,4,5]}];
// comparator
var comp = function(a,b) {
return a.id-b.id;
};
// index for array a
var j = 0;
// sort arrays
a.sort(comp);
b.sort(comp);
// check all elements in b against those with matching ids in a
for(var i=0; i<b.length; i++) {
// find next matching id in a
while(a[j].id<b[i]&&j<a.length) {
j++;
}
// break if no more elements to check against in a
if(j==a.length) {
break;
}
// compare elements with matching ids to see if the books array make the same string
// comparing array references won't work, so they're converted to strings instead
if(a[j].id==b[i].id&&a[j].books.join(",")==b[i].books.join(",")) {
// remove element from b and don't skip over next element
b.splice(i,1);
i--;
}
}
// b should share no elements with a
var data = [
{id: 1, quantity: 10, category: 'A'},
{id: 2, quantity: 20, category: 'B'},
{id: 1, quantity: 30, category: 'A'},
{id: 1, quantity: 30, category: 'Z'},
{id: 2, quantity: 40, category: 'D'}
];
var totalPerType = {};
for (var i = 0, len = data.length; i < len; ++i) {
totalPerType[data[i].id] = totalPerType[data[i].id] || 0;
totalPerType[data[i].id] += data[i].quantity;
}
var out = _.map(totalPerType, function (id, quantity) {
return {'id': id, 'quantity': quantity};
});
console.log(out);
My code currently sums the quantity for objects with the same id. It returns
[ {id:1, quantity:70}, {id:2, quantity:60} ]
How do I get the sum for objects based on both id and category?
For example, I need output like this:
[
{id:1, quantity:40, category:A},
{id:1, quantity:30, category:Z},
{id:2, quantity:20, category:B},
{id:, quantity:40, category:D}
]
I'd like an answer for both plain javascript and underscore.
Using vanilla js.
var tmp = {}
data.forEach(function (item) {
var tempKey = item.id + item.category;
if (!tmp.hasOwnProperty(tempKey)) {
tmp[tempKey] = item;
} else {
tmp[tempKey].quantity += item.quantity;
}
});
var results = Object.keys(tmp).map(function(key){
return tmp[key];
});
Note that this will change objects in original data. Would need to copy item when adding to the tmp object if that is unwanted
var data = [
{id: 1, quantity: 10, category: 'A'},
{id: 2, quantity: 20, category: 'B'},
{id: 1, quantity: 30, category: 'A'},
{id: 1, quantity: 30, category: 'Z'},
{id: 2, quantity: 40, category: 'D'}
];
var tmp = {}
data.forEach(function (item) {
var tempKey = item.id + item.category;
if (!tmp.hasOwnProperty(tempKey)) {
tmp[tempKey] = item;
} else {
tmp[tempKey].quantity += item.quantity
}
});
var results = Object.keys(tmp).map(function(key){
return tmp[key];
});
document.body.innerHTML ='<pre>' + JSON.stringify(results,null,4) +'</pre>';
Two good answers already but I thought I would show a ES6 solution that is a little more flexible. It uses Map and creates a unique key for each record by creating a string from the properties that need to be matched. Adds the records to the map with its key and sums values as required.
When done it converts the map to an array and returns the new array;
var data = [ // test data
{id: 1, quantity: 10, category: 'A'},
{id: 2, quantity: 20, category: 'B'},
{id: 2, quantity: 20, category: 'Z'},
{id: 2, quantity: 20, category: 'D'},
{id: 1, quantity: 30, category: 'A'},
{id: 1, quantity: 30, category: 'Z'},
{id: 2, quantity: 40, category: 'D'}
];
// function to collapse records in an array of objects;
// arr is the array of objects
// match is an array of property names that need to be matched
// sum us an array of property names that need to be summed
function collapse ( arr, match, sum ) { // bad name but just can't remember what this should be called
// define vars
var newArray, key, processRecord
// define function
// function to process each record
processRecord = function( item ) {
// define vars
var key, getKey, sumFields, record;
// define functions
getKey = function ( field ) { key += item[field]; } // Creates a key
sumFields = function ( field ) { record[field] += item[field];} // sums fields
// code
key = ""; // create a blank key
match.forEach( getKey ); // create a unique key
if(newArray.has( key ) ){ // does it exist
record = newArray.get( key ); // it does so get the record
sum.forEach( sumFields ); // sum the fields
}else{
newArray.set( key, item ); // the key does not exist so add new record
}
}
// code
newArray = new Map(); // create a new map
arr.forEach( processRecord ); // process each record
return ( [...newArray.values()] ); // convert map to array and return it
}
// call the function matching id and category summing quantity
var a1 = collapse( data, ["id" , "category"], ["quantity"] );
// call the function matching id only summing quantity
var a2 = collapse( data, ["id"], ["quantity"] );
// call the function matching category only summing quantity
var a3 = collapse( data, ["category"], ["quantity"] );
// call the function matching all fields and summing quantity
var a4 = collapse( data, ["id, "quantity", "category"], ["quantity"] );
Here's a solution using underscore:
// used when calling reduce to sum the quantities of a group of items
var sumQuantity = function(total, item){
return total + item.quantity;
}
// used by groupBy to create a composite key for an item
var itemKey = function(item){
return item.id + '/' + item.category;
}
// used to convert a group of items to a single item
var groupToSummedItem = function(group){
return {
id: group[0].id,
category: group[0].category,
quantity: _.reduce(group, sumQuantity, 0)
}
}
// the bit that does the work
var result = _.chain(data)
.groupBy(itemKey)
.map(groupToSummedItem)
.value();
I have an array with some objects
var arr = [{index: 1, type: 2, quantity: 1}, {index: 3, type: 1, quantity: 2}, {index: 1, type: 3, quantity: 3}];
Now I want to search my array if exists an object inside it with a given index and type. If exists, I add + 1 to the quantity property. If not, I add a new object with quantity of 1. I tried to use, $.grep and $.inArray but to no avail. What is the best way to search the properties in an array of objects?
tnx!
For loop with if condition: JsFiddle
var arr = [{index: 1, type: 2}, {index: 3, type: 1}];
var found = '';
for(item in arr){
if(arr[item].index === 1 && arr[item].type === 2){
found = arr[item];
}
}
In the function in grep you need to return the result of the test and also the result returned from grep is a new array. It does not modify the existing array.
I made a snippet:
var arr = [{index: 1, type: 2}, {index: 3, type: 1}, {index: 1, type: 3}];
var result = $.grep(arr, function(e){
return e.index === 1 && e.type === 3
});
alert(result[0].index + " " + result[0].type);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Is there a way to compare differences between arrays based on changes on their elements positions?
I have an original array of objects which undergoes a change on one of it's element's values, this change is mapped into a new array:
origElements = [{id: 1, value: 50},
{id: 2, value: 60},
{id: 3, value: 70}]
changedElements = [{id: 1, value: 50},
{id: 3, value: 60},
{id: 2, value: 120}]
var diff = _.difference(_.pluck(origElements, "id"), _.pluck(changedElements, "id"));
var result = _.filter(origElements, function(obj) { return diff.indexOf(obj.id) >= 0; });
In this case it is clear why 'result' would return nothing. As there's no difference of values between: [1, 2, 3] and [1, 3, 2]. What I'm trying to achieve here is a 'strict difference' which would look at index as well, thus returning some reference to the new order of the objects.
How about doing it this way:
var origElements = [{
id: 1,
value: 50
}, {
id: 2,
value: 60
}, {
id: 3,
value: 70
}];
var changedElements = [{
id: 1,
value: 50
}, {
id: 3,
value: 60
}, {
id: 2,
value: 120
}];
var origElementsIds = _.pluck(origElements, "id");
var changedElementsIds = _.pluck(changedElements, "id");
console.log("Are array element positions same ?",
origElementsIds.join() === changedElementsIds.join());