配列を制するものはプログラミングを制する


たいていのプログラムではデータ構造として何かしらの配列(または連想配列)が現れて、そして「その全要素に対して何かしらの処理をする」、「配列の全要素を使って、1つの値を算出する」、「配列の中から適切な要素を抽出する」というような処理も頻繁に現れます。言語によっては、これらの処理をきれいに書けるような処理が最初から用意されています。

基本操作 - mapreduceselect

配列全体にかけるような基本的な操作として、mapreduceselectなどがあります。

map

mapは、ある配列に対して、その全要素に同じ演算を行なってその結果を返すような処理です。

#before
[a[0], a[1], a[2], a[3], a[4], a[5]]
# f() をmapする
[f(a[0]),f(a[1]),f(a[2]),f(a[3]),f(a[4]),f(a[5])]

適用する演算を取り替えることで、「全要素を文字列に変換する」とか「全要素を0.1の位で四捨五入する」とか、多様な目的に使えます。

reduce

mapは、ある配列に対して、同じ演算を繰り返し適用して、1つの値を抽出する処理です。

#before
[a[0], a[1], a[2], a[3], a[4], a[5]], initial_value
# f() でreduceする
value = f(initial_value, a[0])
# 初期値を指定せず、a[0]とa[1]でスタートすることもたいてい可能
value = f(value, a[1])
value = f(value, a[2])
value = f(value, a[3])
value = f(value, a[4])
value = f(value, a[5])

これだけではピンとこないと思いますので、具体的なf()の例と、最終結果を挙げてみます。

  • f()で両者の加算→配列全体の総和
  • f()で両者の乗算→配列全体の総乗
  • f()で文字列連結→配列全体を1つの文字列につないだもの
  • f()で大きい方→配列全体の最大値

このように、適切な関数を選ぶことで多様な処理に使えます。なお、初期値として空の配列を与えて、f()として「2つ目の引数の値に適当な演算を行なって、1つ目の配列に追加する」ということを行うと、mapとしても使えます。

select

これは配列と、「要素を取ってtrue/falseを返す関数」を使って、配列の中から条件に合うものだけ抽出するという機能です。関数の符号を逆転させれば、条件に合わないものだけ抽出も容易にできます。

PHP

PHPでは、array_maparray_reducearray_filterという関数があって、それぞれmapreduceselectに使えます。なお、関数リテラルやそれを変数に入れたもの以外を関数として使う場合、名前の文字列、あるいはarray(オブジェクト,'メソッド名')のような配列を渡すことになります(callable)。

// $aの中身をすべてintにする
$b = array_map('intval', $a);

// $c の合計値を計算する(array_sumもあるけど)
$d = array_reduce($a, function($val, $curr){
    return $val + $curr;
});

// $eから文字列だけを抽出する

$f = array_filter($e, 'is_string');

なお、array_mapだけ関数が先に来ていますが、これは複数の配列をとれるという事情が故で、ミスではありません。また、array_filterで関数を省略すると、「truthyなものだけ取り出す」という意味になります。

Ruby

Rubyでは、配列だけでなく、eachメソッドを持つ多くのクラスにEnumerableがmix-inされていて、こちらでmapreduceselectなどが定義されています。

# a の中身をすべて整数にする
b = a.map{|item| item.to_i}
# 上の行と全く同じ
b = a.map(&:to_i) 

# cの合計値を計算する
d = c.reduce{| total, val | total + val }
# 上の行と全く同じ
d = c.reduce(&:+)

# eから 文字列だけを抽出する
f = e.select{|v| v.is_a?(String)}

なお、引数で&:メソッドとすると、{|オブジェクト, *残りの引数| オブジェクト.メソッド *残りの引数}というようなブロックを渡したものとして処理されます(Symbol#to_proc)。また、mapにはcollectreduceにはinjectという別名があります。

CoffeeScript

CoffeeScriptには、「配列内包」という、mapselectを一気に行えるような機能があります。記法としては、
coffeescript
( 式 for 変数 in 配列 when 条件 )

というような形になっています(なお、条件が不要な場合、when 条件は省略できますが、逆に値を加工しなくていい場合はforの前後に同じ変数を書く必要があります)。たとえば、「配列から奇数だけ抽出して、値を3倍して1を足す」ということをする場合、arr2 = (3 * i + 1 for i in arr when i % 2 == 1)とシンプルに書けますが、これをJavaScriptにコンパイルすると

arr2 = (function() {
  var j, len, results;
  results = [];
  for (j = 0, len = arr.length; j < len; j++) {
    i = arr[j];
    if (i % 2 === 1) {
      results.push(3 * i + 1);
    }
  }
  return results;
})();

という膨大な量のコードになります。なお、ES5にもArray.prototype.filter()Array.prototype.map()がありますが、実は(これらの関数には仕様上inでの存在チェックが入ることもあって)ループを書いたほうが速度が出るので、特にCoffeeScriptからこれらの関数を使う必要はあまりないでしょう。Array.prototype.reduce()は使うかもしれませんが(IE8を切ってもいいなら)。