My new blog here at www.mostovenko.com

Sunday, June 23, 2013

reduce in javascript

Despite the simplicity of javascripts nature, this language has nice instruments, that allows us to use the power and expressiveness of some features that came from functional languages. Map, filter, reduce are widely used in functional languages (clojure, haskell, lisp e.t.c).  In this article i will show you some interesting cases of using reduce method.  All examples will be written in both - javascript and coffeescript, to demonstrate how coffeescript improves code expressiveness and readability.



Sequence length:

Let's imagine that javascript Array has no built in method "length" in Array object. That's not a big problem, because we can get it with the help of reduce method:

.js:
a = [10, 20, 30, 40]
a.reduce(funciton(a, b){return ++a}, 0) // 4

.coffee:
a = [1, 2, 3, 4]
a.reduce ((a, b) -> ++a), 0 # 4

 The example above may be not so useful in a real world programming - but shows the key concept of how reduce function is working. It iterates through all elements in the sequence, the result of each iteration will be passed to the next iteration as the first argument and with the next sequence element as the second argument.

Let's take more complex example where we have a list of users, and we want to count how many of them has name - "user1" and how many of them are at the age of 19

.js
var reducer, users;

// Let's create some simple array with users
users = [
  {"name": "user1", "age": 19}, 
  {"name": "user2", "age": 20},
  {"name": "user1", "age": 20}
];

reducer = function(a, b) {
  // setting default count values if aren't set yet
  a.user1_names_count || (a.user1_names_count = 0);
  a.age19_count || (a.age19_count = 0);
  
  // modifying returned value accordingly to our conditions
  if (b.name === "user1") {
    a.user1_names_count += 1;
  }
  if (b.age === 19) {
    a.age19_count += 1;
  }

  return a;
};

users.reduce(reducer, {}) // {"user1_names_count":2,"age19_count":1}
                          // so we have 2 users with name user1 and 1 user with age of 19

.coffee
users = [
    {"name": "user1", "age": 19}
    {"name": "user2", "age": 20}
    {"name": "user1", "age": 20}
    ]

reducer = (a, b) ->
    a.user1_names_count or= 0
    a.age19_count  or=0
    (a.user1_names_count += 1) if b.name is "user1"
    (a.age19_count += 1) if b.age is 19
    a

users.reduce reducer, {} # will produce {"user1_names_count":2,"age19_count":1}


Array Sum

To take advantage of the reduce method let's try to calculate the sum of the array elements:

.js
a = [1, 2, 3, 4]
a.reduce(function(a, b){return a + b}) // will produce 10

.coffee
a = [1, 2, 3, 4]
a.reduce (a, b) -> a + b # will produce 10

And one of the most significant feature of reduce function - that if we want elements multiplication instead of addition - we just need to modify the reduce function to - something like this "function(a, b){return a * b}". And thats it. Combining map, reduce, filter methods provides us extremely powerful and flexible way of working with collections data.

Array reverse

 We can get reversed array easily with the help of the reduce func:

.js:
[1, 2, 3, 4].reduce(function (a, b){return [b].concat(a)}) // [4, 3, 2, 1]
.coffee:
[1, 2, 3, 4].reduce (a, b) -> [b].concat a # [4, 3, 2, 1]


Any

Let's imagine we want to know if there is one true element in the bool sequence:

.js:
l1 = [false, true, false, false]
l2 = [false, false, false, false]

l1.reduce(function(a, b){return a || b}) // true
l2.reduce(function(a, b){return a || b}) // false

.coffee:
l1 = [false, true, false, false]
l2 = [false, false, false, false]

l1.reduce (a, b)-> a or b                # true
l2.reduce (a, b)-> a or b                # false

All

If we will modify an example above a little, we may get function all, that tells us if all elements in sequence are true:

.js:
l1 = [true, true, true, true]
l2 = [false, false, false, false]

l1.reduce(function(a, b){return a && b}) // true
l2.reduce(function(a, b){return a && b}) // false

.coffee:
l1 = [true, true, true, true]
l2 = [false, false, false, false]

l1.reduce (a, b)-> a and b                # true
l2.reduce (a, b)-> a and b                # false

Unique values 

Let's imagine we've got an array, and we need to filter only unique values from it. And the most interesting part of this - we may set some custom unique criteria in reduce function. Let's take a simple example where we have the list of integers and we want to get only the unique values from that list:

.js:
unique_red = function(a, b){return a.indexOf(b)?a.concat(b):a}

[2, 2, 1, 3].reduce(unique_red, []) // will produce [2, 1, 3]


.coffee:
unique_reducer = (a, b) -> unless b in a then a.concat b else a
[2, 2, 1, 3].reduce unique_red, []  # will produce [2, 1, 3]