1
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.

3 Answers 3

3

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>';

Sign up to request clarification or add additional context in comments.

2 Comments

I think you should use some separator between id and category, otherwise 1,23 and 12,3 will be considered equal.
@Oriol possibly, was going with premise that id is numeric and category is not as shown
2

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"] );

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.