配列の要素に対する処理には繰り返し構文よりも高階関数を使ってみよう

  • 48
    いいね
  • 2
    コメント

こんばんはimaizumeです。
プログラミングを勉強していると、どんな言語であっても必ず覚える構文ってありますよね :question:
中でも繰り返し(for, while, until etc)はどんな言語でも必ず出てくる基本構文だと思います。

もし繰り返しの構文がある程度使えるようになったら、次はぜひmapやfilterといった高階関数を覚えてみましょう。
この map や類似の filter, reduceといった関数は 高階関数と呼ばれ、配列の各要素に対してfor文等と同様繰り返し処理を行うためのものです

(:warning: mapは本来写像関数といい、ある配列要素を別の要素に対応付けて変換するための関数一般を表す言葉です。よってreduceやfilterなどの他の高階関数も、厳密には繰り返しと同一ではありませんが、この記事ではあくまで最初に覚えるときのニュアンスを伝えるために、繰り返しの場合と対応付けする形で説明しています。)

ただ同じ処理をしても、高階関数を使えばfor文に比べてコードが見やすくなる場合があります。
また 要素への処理を関数にまとめて使い回しできたり、リファクタリングしやすくなったりする効果もあります :exclamation:
慣れると大変便利なので、for文の使い方を覚えられたら、この記事を参考に mapfilterにもチャレンジしてみてください。

解説の前に

この記事の対象読者

  • mapとかfilterを聞いたことがあるけれど使い方、使いどころがイマイチ分からない
  • 配列の各要素に対して処理を行う部分をもっと簡潔に書きたい
  • 配列に対する繰り返しで使う集計用変数を減らしたい
  • 関数型プログラミングに興味がある

mapなどの高階関数をサポートしている主な言語

サポートがある主要な言語は以下のとおりです。
(筆者が知見のある範囲を中心にした紹介ですので他の言語でもサポートされているものはたくさんあります)

なおこの記事ではJavaScriptを使って解説していきますが、基本的なmap関数の使い方はどの言語でも大体同じですので、適宜自分の言語に読み替えてください (JavaScriptを使う理由は単純にブラウザ上ですぐに実行が可能だからです)

基本的なmap関数の使い方

では、高階関数の基本であるmap関数の使い方を見ていきます。
例えばこんな感じの、配列の中身を出力する簡単なプログラムがあります。

for.js
var x = [1,2,3];
for(var i=0; i < x.length; i++) {
  console.info(x[i]);
}
// 1
// 2
// 3

これをmapで書き直すと...

map.js
var x = [1,2,3];
x.map(function(i){ console.info(i); });
// 1
// 2
// 3

どうでしょう、3行が1行になってちょっとスッキリしませんか :question:
このようにmap関数は、要素に対して行う処理を書いた関数を引数に与えてあげることで、for文同様配列内の各要素に対して同じ処理を繰り返し実行することができます。
今の例ではfunction内の処理は1行でしたが、もちろん複数行に渡る処理を記述しても構いません。

呼び出しのイメージはこんな感じ :arrow_down:

form-of-map.png

図中の青い矢印の方向に処理が進んでいくというイメージを持つと理解がしやすいと思います。
呼び出すとこのように各要素に対して順番に処理がなされていくわけです。

form-of-map-example.png

以上がmap呼び出しの最も基本的な形になります。

結果を新しい配列に格納する

先程は配列内の値を出力しただけでした。
今度はそれぞれの処理結果を新しい配列として受け取りたい場合を考えます。
例えばある配列の各要素の値を2倍し新しい配列として受け取りたい場合、for文を使うとこのようなコードになります。

array_with_for.js
var x = [1,2,3];
var twice = []; // 事前に結果を受け取る変数を定義しておく
for (var i= 0; i<x.length; i++) {
  twice.push(x[i] * 2);
}
console.info(twice); // [2, 4, 6]

forだと事前に新しい配列を入れるための変数を作成しておく必要がありますが、このような場合はmap関数の恩恵を受けることができます。

array_with_map.js
var x = [1,2,3];
var twice = x.map(function(i) { return i*2; });
console.info(twice); // [2, 4, 6];

このように、mapは引数内のfunctionにて行った処理の結果を、そのまま配列にして返却してくれる のです。
ただし先程と違い、最終的に値を返却するためにreturnを付けているのに注意してください、これがないと新しい配列に値が戻りません。
関数内での処理を行ったうえで、最後に値をreturnすることにより、新しい配列の要素を戻すことができるのです。
どうでしょうか、結構便利じゃないですか :question:

return付きのmap関数の呼び出しは、イメージにするとこんな感じになります。

form-of-map-return.png

具体的な返り値を合わせたイメージはこのような感じ

form-of-map-return-example.png

関数を変数に入れる

『関数を変数に入れる』という表現には最初慣れないかもしれないですが、これも覚えておくとさらに便利にmapが使えます。
JavaScriptなどの言語では関数を定義した後、それをまるごと変数に入れて使い回すことができます。

fx = function(x) { return x + 2; }
console.info(fx(1)); // 3
console.info(fx(3)); // 5

先ほどの例で出てきた値を2倍する関数も、一度変数に入れてからmapに渡すことができます。
あとで何度も同じfunction内の処理を使う場合などは、このように関数を変数に入れておくと使い回しができて良いです。

function_obj.js
var fx = function(x) { return x*2; }
console.info([1,2,3].map(fx)); // [2, 4, 6]
console.info([7,4,1].map(fx)); // [14, 8, 2]
console.info([-0.5,0,0.5].map(fx)); // [-1, 0, 1]

filter関数

mapと似たような関数にfilterがあります。
これは各要素に対して条件判定を行い、その判定結果がtrueであったものを返す関数です。
まさに名前の通り、配列から特定の値だけを『フィルタリング』するときに使います。

例えば、配列から2より大きいの値のみを抜き出したいときはこんな感じに書いてあげます。

filter_example.js
var x = [1,2,3];
console.info(x.filter(function(i) { return i > 2; })); // [3];

form-of-filter.png

function内では最終的にtruefalseが返れば良いので、その手間はもっと複雑な処理をしても構いません。
単純な評価であればif文すら不要になるためこちらも大変重宝します

form-of-filter-example.png

複数の関数を組み合わせる

高階関数のスゴい所は、ドットでつなげることで処理した結果をさらに続けて次の処理へ繋げられることです(メソッドチェーン)。

multiple_maps.js
var fx = function(x) { return x * 10; } // 値を10倍
var gx = function(x) { return x > 15; } // 15より大きければtrueを返す
console.log([1,2,3].map(fx).filter(gx)); // [20, 30]

最初のうちはあまりメソッドチェーンを使う機会がないかもしれませんが、配列を加工する処理が複雑化してきたときにこの仕組みが役に立ちます。
変数を経由しないでも連続的に処理を記述できるため、コードの中で見なければならない部分が減ることが重要です。

map, filter以外の高階関数

最後に上記で紹介したmapとfilter以外の高階関数についても少しだけ紹介しておきます。

forEach

冒頭の例で出たような新しい配列へと変換をしない (=中の関数が何もreturnしない) 場合はforEachの方が適切です。
使い方はmapの場合と同じです。

var x = [1, 2, 3];
x.forEach(function(value, index, array) { console.info(x); });
// 1
// 2
// 3

functionの引数が増えていますが、それぞれ配列の各要素、インデックス番号、参照している配列を参照するための変数です。

reduce

配列の中の値をまとめて1つの値を返したいような処理を行うための関数です。
例えばこのようにforの外側に変数を作って値を集計するような場合

var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var sum = 0;
for(i=0; i<x.length; i++) {
  sum += x[i];
}
console.info(sum); // 55

reduceを使うとこんなにスッキリ書くことができます。

var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.info(x.reduce(function(prev, i){ return prev + i; })); // 55

mapやfilterと違い、functionの引数が2つに増えていますが、それぞれ直前に返却した値(prev: forを使った場合のsumに相当する変数)と各要素の値(i)をとります。
この関数は2番目の要素から適用され、prevの初期値は最初の要素の値になります。
そして集計をする場合は前の値に今の要素の値を足して返却していくだけで最後に集計値が返ります。

every, some

everysomefilterに近い関数で、全ての要素がfunction内でtrueを返せばtrue (=AND判定)、someは要素のうち1つでもtrueを返せばtrue (=OR判定)を返します

例えば配列の中の全ての要素が3以上であるかを判定するにはeveryをこのように使います。

var x = [2, 3, 4]
console.log(x.every(function(i){ return i >= 3; })); // false (2があるので)
var y = [4, 5, 6]
console.log(y.every(function(i){ return i >= 3; })); // true (全て3以上)

1つでも条件を満たす要素があるかどうかはsomeでこのように判定します。

var x = [0, 1, 2]
console.log(x.some(function(i){ return i >= 3; })); // false (どれも3未満)
var y = [1, 2, 3]
console.log(y.some(function(i){ return i >= 3; })); // true (3があるので)

おわりに

以上高階関数のmapとfilterについて、入門的な説明でした。
普段のプログラミングでも使えそうな場面はたくさんあるのではないでしょうか。
ぜひfor文で書いている所を書き直しコードの見通しを良くしたり、変数やif文を減らしてみたりしてください。

参考