How to Transform Just About Any Data Structure with Reduce

Fold is a common higher-order function widely used in functional programming. In Javascript, we have an analogous function called reduce.

While not specific to Ember, reduce is an amazingly helpful tool. It is so versatile that it allows us to transform data structures in virtually any way we want.

This is the anatomy of a reduce:

array.reduce(function(acc, value, index, array) {
  // ...
  return acc;
}, initialValue);

The supplied function will be called once per element in the array:

Understanding with examples

It can sum an array (from Array to Number):

[1, 2, 3].reduce(function(acc, value) {
  return acc + value;
}, 0);
// => 6

or find a max:

[1, 2, 7, 3].reduce(function(acc, value) {
  if (acc < value) acc = value;
  return acc;
}, -Infinity);
// => 7

It can flatten nested objects (from Array or Object to Array):

const nested = {
  id: 1,
  children: [
    { id: 2 },
    { id: 3,
      children: [{ id: 5 }, { id: 6 }]
    },
    { id: 4 }
  ]
}

const flatten = function(obj) {
  const array = Array.isArray(obj) ? obj : [obj];
  return array.reduce(function(acc, value) {
    acc.push(value);
    if (value.children) {
      acc = acc.concat(flatten(value.children));
      delete value.children;
    }
    return acc;
  }, []);
}

flatten(nested);
// => [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }, { id: 6 }, { id: 4 } ]

It can implement map:

const map = function(array, fn) {
  return array.reduce(function(acc, value) {
    value = fn.call(this, value);
    acc.push(value);
    return acc;
  }, []);
}

// add 1 to each number
map([1, 2], function(n) { return n + 1 });
// => [2, 3]

…or filter:

const filter = function(array, fn) {
  return array.reduce(function(acc, value) {
    const ok = fn.call(this, value);
    if (!!ok) acc.push(value);
    return acc;
  }, []);
}

// return only even numbers
filter([1, 2, 5, 7, 8, 10, 104, 1189], function(n) { return n % 2 === 0 });
// => [ 2, 8, 10, 104 ]

It can join strings:

["hello", "this", "is", "awesome"].reduce(function(acc, value) {
  if (acc) acc = acc.concat(" ");
  acc = acc.concat(value);
  return acc;
}, "");
// => 'hello this is awesome'

And even call promises serially:

var models = [model1, model2, model3];

models.reduce(function(previous, model) {
  return previous.then(function() {
    return model.save();
  });
}, RSVP.resolve());

In Ember

Say we have a template displaying a list of spare parts (each with its name, sku and amountInStock).

In the component (or controller) that backs that template we can define totalAmountInStock:

totalAmountInStock: Ember.computed('parts.@each.amountInStock', function() {
  return this.get('parts').reduce(function(acc, value) {
    return acc + value.get('amountInStock');
  }, 0);
});

// and then {{totalAmountInStock}} in the template!

VoilĂ , the total amount gets effortlessly updated!

(Yes, I know this can be done with Ember.computed.sum… hey, I needed an easy example!)

To sum up, if you’ll pardon the pun, whenever you need to transform data (even from one data type to another) there is one tool that will always come handy: that is reduce!

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)