あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

reduce関数は結構有用っていうお話

JavaScriptに限った話ではないのですが、reduce関数を持つプログラミング言語がいくつかあります。 JavaScriptに関しては、一応、ECMAScript5の仕様に登場するようで、将来的にはどのブラウザでも使えるようになりそうな気配はあります。

また、MDCではreduceのアルゴリズムが掲載されているので、これを利用すれば現時点でもどのブラウザでもreduce関数を利用することができます。

reduce関数とは?

MDCに掲載されている文章を引用します。

配列の(左から右へ) 2 つの値に対して同時に関数を適用し、単一の値にします。

JavaScriptのreduceは、配列のメソッドです。左ら右へとありますが、右から左へ関数を適用するreduceRightという関数もあります。*1

どういう時に使えるか

元となる配列があって、それを累積して新しい単一の値を作りたいって時に使えます。

よくサンプルで見かける、配列の値を合計するsum関数をreduceを使わないパターンと、reduceを使うパターンで例示してみます。

reduceを使わないパターン

こんな感じでしょうか。

var ary = [1,2,3,4,5];
function sum(a) {
  var result = 0;
  for (var i = 0, l = a.length; i < l; i++) {
    result += a[i];
  }
  return result;
}
sum(ary);
// 15
reduceを使ったパターン

reduceを使ったパターンでは、これがかなり短く書けます。

var ary = [1,2,3,4,5];
function sum(a) {
  return a.reduce(function(x, y) { return x + y; });
}
sum(ary);
// 15

なかなかの表現力を持った関数ですね。

一応、どのような流れになるか、値を展開してみたものをコメントに書いてみました。
reduceに渡す関数の引数にそれぞれどんな値が代入されて、返り値が何かを追記しました。

var ary = [1,2,3,4,5];
function sum(a) {
  return a.reduce(function(x, y) { return x + y; });
}
sum(ary);
// 15

// 初回
// x = 1, y = 2
// 累積値: 3

// 2回目
// x = 3(累積値), y = 3
// 累積値: 6

// 3回目
// x = 6(累積値), y = 4
// 累積値: 10

// 4回目
// x = 10(累積値), y = 5
// 累積値: 15

先ほど書いたように、元となる配列があって、それを累積して新しい単一の値を作りたいって時に使えます。

他にも、reduce関数を使えば、以下のような関数を簡単に作ることができます。

function max(a) {
  return a.reduce(function(x, y) {
    if (x > y) return x;
    return y;
  });
}
max([1,2,3,4,5]);
// 5

function min(a) {
  return a.reduce(function(x, y) {
    if (x > y) return y;
    return x;
  });
}
min([1,2,3,4,5]);
// 1

function average(a) {
  return a.reduce(function(x, y) {
    if (y === a[a.length-1]) return (x + y) / a.length;
    return x + y;
  });
}
average([1,2,3,4,5]);
// 3

function exist(a, val) {
  return a.reduce(function(x, y) {
    return (x === true || x === val || y === val) ? true : false;
  });
}
exist([1,2,3,4,5], 5);
// true

for文で実装するのに比べて、非常に簡潔ですね。

ただ、existの例は微妙です。for inで事足りるし。

あと、reduce関数はbreakができないので、場合によっては処理が冗長になることがあるので注意が必要です。

単一の値は配列やオブジェクトでも良い

こういうサンプルばかり見ていると見落としてしまいがちですが、reduce関数で累積して返す単一の値というのは、配列などでもOKです。

MDCには多次元配列を一次元配列にするという関数の例が載っています。
元となる配列が多次元配列で、最後に返す値が一次元配列ということですね。

// from https://developer.mozilla.org/ja/Core_JavaScript_1.5_Reference/Objects/Array/reduce#section_8
var flattened = [[0,1], [2,3], [4,5]].reduce(function(a,b) {
  return a.concat(b);
}, []);
// flattened is [0, 1, 2, 3, 4, 5]

他にも配列を返す例として、こんな関数がシンプルな記述で作れます。

function filter(a) {
  a.unshift([]);
  return a.reduce(function(x, y) {
    if (y) {
      x.push(y);
      return x;
    }
    return x;
  });
}

ただ、filter関数は、ECMAScript5の仕様で、Array.prototype.filterとして定義されているので、わざわざ自作する必要はないです。

まとめ

reduce関数は、元となる配列があって、それを累積した単一の値(配列も可)を返したいというニーズがあった場合に、シンプルにその処理を実現できるのでcoolと思った次第です。
実際には配列を返すケースはmapを使えばいいんですけどね。

汎用的な関数を作るときに使ってみてはいかがでしょうか。

*1:個人的にはこの使い分けがわかりません。。。reverseすればよくね?っていう発想じゃだめですか><