Sum object of array by same element - javascript

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();

Related

Push an array of objects by it's value to another array

I am getting unexpected results from my below program. Both inputs and outputs arrays are getting same value. Somehow from this I got to know that it's the problem of passing a reference. My question is how can I achieve the same with below approach. Appreciate your time.
Expecting inputs array to be [[{id: 1, name: 'foo'}, {id: 2, name: 'bar'}], [{id: 1, name: 'foo'}]]
var inputs = [];
var outputs = [];
var exampleArr = [{id: 1, name: 'foo'}, {id: 2, name: 'bar'}];
function analyze(listArr) {
inputs.push(...listArr);
outputs = [...outputs, ...listArr];
outputs.forEach((item) => {
item.name = 'hello';
});
console.log('inputs', inputs);
console.log('ouputs', outputs);
listArr.pop();
if(listArr.length) analyze(listArr);
}
analyze(exampleArr);
Clone all objects in listArr (one nesting level only) before appending to outputs.
var inputs = [];
var outputs = [];
var exampleArr = [{id: 1, name: 'foo'}, {id: 2, name: 'bar'}];
function analyze(listArr) {
inputs.push(...listArr);
// clone objects in array, one level
outputs = [...outputs, ...listArr.map(x=>typeof x==='object'? {...x}:x)];
outputs.forEach((item) => {
item.name = 'hello';
});
console.log('inputs', inputs);
console.log('ouputs', outputs);
listArr.pop();
if(listArr.length) analyze(listArr);
}
analyze(exampleArr);

Javascript create array from existing split on property value

I'm trying to iterate over an existing array with of objects with a 'quantity' property and rebuild it by a control value.
let cart = [{id: 1, name: 'Pizza', quantity: 5, specialId: 0},
{id: 2, name: 'Burger', quantity: 2, specialId: 0}];
I have a control of 3 items i.e. for every 3 items you get a discount so I'd like to reconstitute the cart array as follows:
cart = [{id: 1, name: 'Pizza', quantity: 3, specialId: 1},
{id: 2, name: 'Pizza', quantity: 2, specialId: 2},
{id: 3, name: 'Burger', quantity: 1, specialId: 2},
{id: 4, name: 'Burger', qty: 1, specialId: 0}]
I've looked at several ways of doing this mostly around creating a new array of single quantity items and then creating another final array but surely that isn't very efficient?
I'd appreciate any pointers. I have a horrible feeling I'm missing something simple and have stared at this too long.
If I understand correctly the amount of three is ignorant of the type of product, so the second batch of three (in your example) consists of 2 pizzas and 1 burger.
The specialId seems to be unique and non-zero for every complete set of three (where every item in that set shares that specialId value), and zero for any remaining item(s).
Finally, it seems that the id in the result is unrelated to the input, but just an incremental number.
Here is how you could do that:
function splitBy(cart, size) {
const result = [];
let quantity = 0;
let grab = size;
let specialId = 1;
let id = 1;
for (let item of cart) {
for (quantity = item.quantity; quantity >= grab; quantity -= grab, grab = size, specialId++) {
if (result.length && !result[result.length-1].specialId) result[result.length-1].specialId = specialId;
result.push(Object.assign({}, item, {quantity: grab, specialId, id: id++}));
}
if (quantity) result.push(Object.assign({}, item, {quantity, specialId: 0, id: id++}));
grab = size - quantity;
}
return result;
}
const cart = [{id: 1, name: 'Pizza', quantity: 5, specialId: 0},
{id: 2, name: 'Burger', quantity: 2, specialId: 0}];
const result = splitBy(cart, 3)
console.log(result);
Basically you have two options.
loop over the current cart, and if the quantity is over 3, split it to two, and push them both.
split the array, and then merge it together.
My guess is to go with the first option, doing something like this:
var cart = [{id: 1, name: 'Pizza', quantity: 5, specialId: 0},
{id: 2, name: 'Burger', quantity: 2, specialId: 0}];
var a = [];
cart.forEach(x => {
if (x.quantity > 3) {
let temp = {...x};
temp.quantity = 3;
a.push(temp);
x.quantity -= 3;
}
a.push(x)
});

How to convert values to array of objects

Suppose I have this data:
var id = 81;
var categories = [1, 2, 3, 4, 5];
How do I transform this into:
[{id: 81, category: 1}, {id: 81, category: 2}, {id: 81, category: 3}, {id: 81, category: 4}, {id: 81, category: 5}]
Is there an elegant way to do this using underscore or lodash?
No need for libraries here.
const result = categories.map(x => ({ id, category: x }))
Working Example JSBin
var id = 81;
var categories = [1, 2, 3, 4, 5];
var arr = [];
for (var i = 0; i < categories.length; i++) {
arr.push({id: id, category: categories[i]});
}
Or:
var a = categories.map(function(a) {
return {id: id, category: a};
});
You don't need any library, just good, old Vanilla JS.
var newArray = categories.map(function(item) {
return {id: id, cetegory: item}
});
Using Lo-Dash/Underscore, the code would be:
var result = _.map(categories, x => ({ id, category: x }));
But this is actually longer than the pure JS solution (from Роман Парадеев):
var result = categories.map(x => ({ id, category: x }));

How do I make a link between the items in two different arrays?

I have these two arrays:
var Names = ['jack', 'peter', 'jack', 'john'];
var Ids = ['1' , '2' , '3' , '4' ];
Also I have this variable:
var name = 'ja'; // to search in the Names array
I search name variable in the Names array as follow:
var MatchesNames = Names.filter(function(x){
if(x.indexOf(name) >= 0) return x
}).slice(0,4); // slice() makes limit the results
The output of above code is:
alert(MatchesNames);
//=> jack,jack
As you see there is two duplicate names (which have different ids) .. Now I need to pull their ids out from Ids array. How can I do that? I guess I need an object but really I don't know how can I use an object in this case.
I want this output:
//=> jack(1),jack(3)
How can I do that?
You can use the Array index as id, and process it like:
var names = ['jack', 'peter', 'jack', 'john'];
var ids = ['1' , '2' , '3' , '4' ];
var name = 'ja';
var result = [];
var zippedArray = names.map(function (e, i) {
return [names[i], ids[i]];
});
zippedArray.map(function(element, index) {
if(element[0].indexOf(name) >= 0) {
result.push( element[0] + "(" + element[1] + ")" );
}
});
console.log(result.toString());
// jack(1), jack(3)
If you want to associate the name with the id, you should really use an object; otherwise you will have to assume that the id array and names are in the correct order, but it would be difficult to guarantee.
var people = [
{ name: 'jack', id: 1 },
{ name: 'peter', id: 2 },
{ name: 'jack', id: 3 },
{ name: 'john', id: 4 }
];
var name = 'ja';
var matchedPeople = people.filter((person) => person.name.includes(name));
var matchedIds = matchedPeople.map((person) => person.id);
You could also incorporate Ramda/Lodash to compose these functions
Approach with an Array#forEach() and a result array with objects.
function find(o, s) {
var r = [];
o.names.forEach(function (a, i) {
if (~a.indexOf(s)) {
r.push({ id: o.ids[i], name: a });
}
});
return r;
}
var Names = ['jack', 'peter', 'jack', 'john'],
Ids = ['1', '2', '3', '4'],
result = find({ ids: Ids, names: Names }, 'ja');
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
document.write(result.map(function (a) { return a.name + '(' + a.id + ')'; }).join(','));
Check the documentation for .filter, there's an option to call with the array index.
(It also returns true/false, not the actual value, so current usage is wrong, but a side issue).
This way, you can get an array of the IDs that match, at the time they match:
var Names = ['jack', 'peter', 'jack', 'john'];
var Ids = ['1' , '2' , '3' , '4' ];
var name = 'ja';
var MatchesIds = [];
var MatchesNames = Names.filter(function(x, i){
console.log("Check: " + i + " : " + x)
if(x.indexOf(name) >= 0) {
MatchesIds.push(i)
return true
}
})
console.log(MatchesNames)
console.log(MatchesIds)
Example fiddle: https://jsfiddle.net/nkuntaup/
I'd use reduce:
var Names = ['jack', 'peter', 'jack', 'john'];
var Ids = ['1', '2', '3', '4'];
var search = name => Names.reduce((r, n, i) =>
n.indexOf(name) > -1 ? (r.push([n, Ids[i]]), r) : r, []);
results.innerHTML = JSON.stringify(search('ja'));
<pre id="results"></pre>
You may want something like this.
var Names = [{name: 'jack', id: 1}, {name: 'peter', id: 2}, {name: 'jack', id: 3}, {name: 'john', id: 4}];
var chars = 'ja';
var MatchesNames = Names.filter(function(x) {
if(x.name.indexOf(chars) >= 0) {
return x;
};
}).slice(0,4);
MatchesNames.forEach(function(person) {
console.log(person.name + '(' + person.id + ')');
});
I am not a professional but this may help. By the way you'll have to make some retouches.
var Names = ['jack', 'peter', 'jack', 'john'];
var Ids = ['1' , '2' , '3' , '4' ];
function fakeZip(arr1, arr2) {
var ret = new Object;
for (var i=0; i<arr1.length; i++) {
ret[arr2[i]] = arr1[i];
}
return ret;
}
var newobj = fakeZip(Names, Ids);
JSON.stringify(newobj);
/*
{"1":"jack","2":"peter","3":"jack","4":"john"}
*/
function lookFor(el, obj) {
res = []
for(key in obj) {
if(obj[key]== el ){
res.push(key)
}
}
return [el, res];
}
lookFor("jack", newobj)
/*
jack,1,3
*/

Merge duplicates in JavaScript Array

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.

Categories

Resources