3

I have an array like the following.

[ { sku: 'TEA-BLCK', price: '10', quantity: '1' },
  { sku: 'TEA-ELGY', price: '10', quantity: '1' },
  { sku: 'TEA-CHAI', price: '10', quantity: '1' },
  { sku: 'TEA-GREN', price: '10', quantity: '1' },
  { sku: 'TEA-ELGY', price: '10', quantity: '1' },
  { sku: 'TEA-MINT', price: '10', quantity: '1' } ]

I need to make it look like this

[ { sku: 'TEA-BLCK', price: '10', quantity: '1' },
  { sku: 'TEA-ELGY', price: '10', quantity: '2' },
  { sku: 'TEA-CHAI', price: '10', quantity: '1' },
  { sku: 'TEA-GREN', price: '10', quantity: '1' },
  { sku: 'TEA-MINT', price: '10', quantity: '1' } ]

I got so far to make this reduce function using underscore.js.

var reduce = function(){
    return _.reduce(line_items, function(quant, item) {
        quant[item.sku] = (typeof(quant[item.sku]) !== "undefined") ? quant[item.sku] : 0 ;
        quant[item.sku] = parseFloat(item.quantity) + quant[item.sku];
        return quant;
    }, {});
}

Which spits out the following.

{ 'TEA-BLCK': 1,
  'TEA-ELGY': 1,
  'TEA-CHAI': 2,
  'TEA-GREN': 1,
  'TEA-MINT': 1 }

Is this a good job for reduce? How can I get it the way I want?

1
  • 3
    reduce is not well suitable for this task. Consider starting with groupBy and then map. Commented Jan 4, 2013 at 20:18

6 Answers 6

3

Try something like this:

var line_items = [
    { sku: 'TEA-BLCK', price: '10', quantity: 2 },
    { sku: 'TEA-ELGY', price: '10', quantity: 3 },
    { sku: 'TEA-CHAI', price: '10', quantity: 1 },
    { sku: 'TEA-GREN', price: '10', quantity: 1 },
    { sku: 'TEA-ELGY', price: '10', quantity: 1 },
    { sku: 'TEA-MINT', price: '10', quantity: 1 }
];

// Group items by sku.
var line_items_by_sku = _(line_items).groupBy(function (item) { return item.sku; });

// Get the new line_items array based on the grouped data.
line_items = _(line_items_by_sku).map(function (items) {
    var item = items[0];
    item.quantity = _.reduce(items, function(memo, item){ return memo + item.quantity; }, 0);
    return item;
});

See this jsfiddle.

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

1 Comment

Very true. The updated jsfiddle already had it fixed, so I replaced with that. Thanks!
2

What about a simple solution like this:

function reduce(array) {
    var out = [];
    var indexBySku = {};
    for (var i = array.length; i--;) {
        if (!indexBySku[array[i].sku]) {
            indexBySku[array[i].sku] = out.length;
            out.push(array[i]);
        } else {
            out[indexBySku[array[i].sku]].quantity -= -array[i].quantity;
        }
    }
    return out;
}

http://jsfiddle.net/waV6H/1

Probably the only thing that needs explanation here is the line where we subtract a negative instead of adding. This is just to avoid string concatenation, since the values in "quantity" are strings.


Super condensed version, dropping unneeded braces and semicolons... just for fun. Preserves order and quantity as string, works with quantity != 1.

function reduce(array) {
    var out = [], indexBySku = {}, len = array.length, i, j
    for (i = 0; i < len; i++)
        (j = indexBySku[array[i].sku]) ?  
            out[j].quantity = out[j].quantity - -array[i].quantity + '' :
            indexBySku[array[i].sku] = out.push(array[i]) - 1
    return out
}

2 Comments

This solution is missing the item with sku TEA-BLCK.
@ThomasReggi no problem, thought you might like a plain vanilla solution :)
1

I was a bit late to the party, but it sounded so fun, so..

JSON.stringify(
[ { sku: 'TEA-BLCK', price: '10', quantity: '1' },
  { sku: 'TEA-ELGY', price: '10', quantity: '1' },
  { sku: 'TEA-CHAI', price: '10', quantity: '1' },
  { sku: 'TEA-GREN', price: '10', quantity: '1' },
  { sku: 'TEA-ELGY', price: '10', quantity: '1' },
  { sku: 'TEA-MINT', price: '10', quantity: '1' } ].sort( function (a, b) {
   return a.sku < b.sku;
} ).reduce( function (a,v) {
   if ( !a[0] || a[0].sku !== v.sku  ) {
      a.unshift(v);
   } else {
      // notice the very inefficient str -> number -> str conversion
      a[0].quantity = 1*a[0].quantity + 1*v.quantity + '';
   }

   return a;
 }, []) );

Here we pre-sort the array, and then add elements to the beginning of accumulator. And GGG have already said about strings.

http://jsfiddle.net/EqGUe/

3 Comments

Nice... functional, no dependencies, meets all requirements, works for quantities other than 1, preserves quantities as strings. ES5-only, though. +1
@GGG yes, but with es5shim you can give reduce even to IE6.
I don't think IE has Array#reduce until like 9... so maybe dependencies after all ;)
1

A bit late as well, but fun to work out http://jsbin.com/amazat/2/

line_items_reduced = _.map(reduce(), function(value, key) {
  var line_item = _.find(line_items, function(line_i){
    return (key === line_i.sku);
  });
  line_item.quantity = value;
  return line_item;
});

Comments

1

This updates benekastah's answer and handles quantities more than 1... But I did take the liberty to change quantity to an integer

http://jsfiddle.net/xjUDY/3/

var line_items = [
    { sku: 'TEA-BLCK', price: '10', quantity: 2 },
    { sku: 'TEA-ELGY', price: '10', quantity: 3 },
    { sku: 'TEA-CHAI', price: '10', quantity: 1 },
    { sku: 'TEA-GREN', price: '10', quantity: 1 },
    { sku: 'TEA-ELGY', price: '10', quantity: 1 },
    { sku: 'TEA-MINT', price: '10', quantity: 1 }
];

// Group items by sku.
var line_items_by_sku = _(line_items).groupBy(function (item) { return item.sku; });

// Get the new line_items array based on the grouped data.
line_items = _(line_items_by_sku).map(function (items) {
    var item = items[0];
    item.quantity = _.reduce(items, function(memo, item){ return memo + item.quantity; }, 0);
    return item;
});

document.write("<pre>"+JSON.stringify(line_items, null, " ")+"</pre>");
​

1 Comment

Even though in my code there could never be more then one quantity. I prefer this.
0

Technically this is the complete answer. From string to string. Sorry guys.

var line_items = [
    { sku: 'TEA-BLCK', price: '10', quantity: "2" },
    { sku: 'TEA-ELGY', price: '10', quantity: "3" },
    { sku: 'TEA-CHAI', price: '10', quantity: "1" },
    { sku: 'TEA-GREN', price: '10', quantity: "1" },
    { sku: 'TEA-ELGY', price: '10', quantity: "1" },
    { sku: 'TEA-MINT', price: '10', quantity: "1" }
];

// Group items by sku.
var line_items_by_sku = _(line_items).groupBy(function (item) { return item.sku; });

// Get the new line_items array based on the grouped data.
line_items = _(line_items_by_sku).map(function (items) {
    var item = items[0];
    item.quantity = _.reduce(items, function(memo, item){ return memo + parseFloat(item.quantity); }, 0);
    item.quantity = item.quantity.toString();
    return item;
});

document.write("<pre>"+JSON.stringify(line_items, null, " ")+"</pre>");

1 Comment

Why all that parseFloat / toString stuff and not just return memo - -item.quantity + '';

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.