The future starts today

Webとか英語とか育児とかに関する雑記

関数の魅力を引き出すmap, filter, reduce

f:id:shibe97:20170302175949j:plain

JavaScriptの関数は第一級オブジェクトである。

変数に関数を代入できるし、関数の引数に関数を与えられるし、関数の返り値として関数を返せる。

ES2015で追加されたmap, filter, reduceを使うと今まで何だか綺麗に書けなかったものがスッキリいい感じに書けるようになる。

よくあるJSON配列をごにょごにょと変換する例を挙げていこう。

対象のJSON配列:

const list = [
    {
        id : "1",
        name : "hoge",
        status : "active",
        num : 13,
        children : ["a", "c", "e"]
    },
    {
        id : "2",
        name : "fuga",
        status : "stop",
        num : 22,
        children : ["a", "g"]
    },
    {
        id : "3",
        name : "piyo",
        status : "active",
        num : 57,
        children : ["e", "h"]
    }
];

map

mapを使うと、ある配列を新しい配列に変換できる。 要素数を変えずに他の配列書き換えたい場合に便利。

idだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push(list[i].id);
}

console.log(ids);   // [ '1', '2', '3' ]

mapを使った書き方

const ids = list.map((item) => item.id);

console.log(ids);   // [ '1', '2', '3' ]

idとnameだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push({
        id : list[i].id,
        name : list[i].name
    });
}

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

mapを使った書き方

const ids = list.map(({id, name}) => ({id, name}));

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

filter

filterを使うと、ある配列から必要なものだけを選んだ新しい配列を作ることができる。

statusがactiveのものに絞り込んでidを取得したい

従来の書き方

var activeList = [];
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        activeList.push(list[i].id);
    }
}

console.log(activeList);   // [ '1', '3' ]

filterを使った書き方(mapも用いる)

const activeList = list.filter((item) => item.status === 'active')
                       .map((item) => item.id);

console.log(activeList);   // [ '1', '3' ]

statusがactiveのものがあるかどうかをBool値で取得したい

従来の書き方

var flag = false;
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        flag = true;
    }
}

console.log(flag);   // true

filterを使った書き方(mapも用いる)

const flag = 
    list.filter((item) => item.status === 'active')
    .length > 0;

console.log(flag);   // true

reduce

reduceは配列内の全要素を用いて値やオブジェクトを生成する。 使い方が難しいが、使いこなせると強い。

各要素が保持しているnumの合計値を取得したい

従来の書き方

var sum = 0;
for (var i = 0; i < list.length; i++) {
    sum += list[i].num;
}

console.log(sum);   // 92

reduceを使った書き方(mapも用いる)

const sum = list.map((item) => item.num)
                     .reduce((x, y) => x + y);

console.log(sum);   // 92

各要素が保持しているchildren配列を連結したい

従来の書き方

var children = [];
for (var i = 0; i < list.length; i++) {
    children = children.concat(list[i].children);
}

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

reduceを使った書き方(mapも用いる)

const children = list.map((item) => item.children)
                     .reduce((x, y) => [...x, ...y]);

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

まとめ

for文やforEach文はループ処理であり、returnが存在しないため、ループ処理後は一度式を途切らせなくてはならない。
しかし、mapfilterreduceを用いると次の処理にそのままつなげることができる。
関数型の入り口として知っておいて損はない。

また、ほとんどの処理を1行で書くことができるため、コードが簡潔で済む。
慣れていないと解読する側は大変だが、mapfilterreduceさえ抑えておけば、後々活きてくるだろう。