Question

I want to reduce this object to just an object containing product name and average price. What's the fastest way to do it?

var foo = { group1: [
        {
            name: "one",
            price: 100
        },
        {
            name: "two",
            price: 100
        }],
       group2: [
        {
            name: "one",
            price: 200
        },
        {
            name: "two",
            price: 200
        }],
       group3: [
        {
            name: "one",
            price: 300
        },
        {
            name: "two",
            price: 300
        }]
      }

resulting in

var foo2 = [{  
               name: 'one',  
               price: 200
            },{
               name: 'two', 
               price: 200
            }];

Thanks!

Was it helpful?

Solution 3

Edit: Leaving this up for now, but I totally forgot about _.flatten, so redmallard's got a much better answer.

If you already know the product names and they appear in every group, you could do the whole thing quickly this way:

var productAveragePrices = function ( groups, names ) {
  return _.map( names, function ( name ) {
    var product = { name: name }, productPricesSum = 0;
    _.each( groups, function ( group ) {
      productPricesSum += ( _.findWhere( group, product ).price );
    });
    product.price = productPricesSum / _.size( groups );
    return product;
  });
};
var foo2 = productAveragePrices = function ( foo, ['one', 'two'] );

I put this together, which should work even if your groups have different products (eg "one" in first, second, and fourth group and "two" in first and third):

var productPriceReducer = function( memo, group ) {
  _.each( group, function( product ) {
    // Grabs the current product from the list we're compiling
    var memoProduct = _.findWhere( memo, { name: product.name });
    if ( !memoProduct ) {
      // If the product doesn't exist, creates a holder for it and its prices
      memoProduct = {
        name: product.name,
        prices: [ product.price ]
      };
      memo.push( memoProduct );
    } else {
      // Otherwise, it just adds the prices to the existing holder.
      memoProduct.prices.push( product.price );
    }
  });
  return memo;
};

// This gets us a list of products with all of their prices across groups
var productPrices = _.reduce( foo, productPriceReducer, [] );

// Then reducing to the average is pretty simple!
var productAveragePrices = _.map( productPrices, function ( product ) {
  var sumPrices = _.reduce( product.prices, function ( memo, price ) {
    return memo + price;
  }, 0 );
  return {
    name: product.name,
    price: sumPrices / product.prices.length
  };
});

You could still do the above in one function with a counter and summing the prices, but this way, you also have the prices in case you want to, say, take the standard deviation or find the mode.

OTHER TIPS

Not to rain on Evan's parade, but here's an alternative that is a bit shorter ;)

result = _.chain(original)
  .flatten()
  .groupBy(function(value) { return value.name; })
  .map(function(value, key) {
    var sum = _.reduce(value, function(memo, val) { return memo + val.price; }, 0);
    return {name: key, price: sum / value.length};
  })
  .value();

See it in action: http://plnkr.co/edit/lcmZoLkrlfoV8CGN4Pun?p=preview

I really like redmallard's solution, but I wanted to golf a little.

Underscore doesn't include a sum function, but we can write pretty elegant functional expressions by adding a sum mixin. This function is known as add in the underscore-contribs repo.

Then we can write:

// Somewhere in the initialization of the program
_.mixin({
  sum : function (arr) {
     return _.reduce(arr, function (s, x) { return s + x;}, 0);
  }
});
result = _.chain(original)
  .flatten()
  .groupBy('name') // shorthand notation
  .map(function (value, key) {
      var sum = _.chain(value).pluck('price').sum().value();
      return { name: key, price: sum / value.length}; 
  })
  .value();

http://plnkr.co/edit/ul3odB7lr8qwgVIDOtM9

But then we can also create an avg mixin to expand our toolbelt:

// Somewhere in the initialization of the program
_.mixin({
  sum : function (arr) {
     return _.reduce(arr, function (s, x) { return s + x;}, 0);
  },
  avg : function (arr) {
     return _.sum(arr)/arr.length;
  }
});
result = _.chain(original)
  .flatten()
  .groupBy('name') // shorthand notation
  .map(function (value, key) {
      return { name: key, price: _.avg(value)}; 
  })
  .value();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top