【JavaScript】イイ感じに配列を扱うイディオムまとめ。map filter reduce... 

## はじめに

`Array`の`map`、`reduce`、`filter`などを使って配列をイイ感じに操作する方法をまとめました。
`for`文を使うと「どうやるか」といったコードになりがちですが、`map`とか`filter`とか使うと「何をするか」が宣言的に書けます。
（※`for`文にも無限を扱えたり、`yield`とか早いとかいいところはあると思います。）

もっとイケてる書き方があるという場合は教えてくれると嬉しいです。

詳しい構文はMDN のリンクを張ったのでそちらが参考になると思います。
**前置き①②**はアロー関数と高階関数です。
`Array`の`map`や`reduce`などは関数を引数とするので切り離せない存在です。
 
## 前置き①：アロー関数

以下の関数をアロー関数に変形します。

```js:二乗を返す関数
function square(num) {
  return num ** 2;
}
```

変数に匿名関数を代入しても同じです。

```js
const square = function(num) {
  return num ** 2;
};
```

アロー関数では`function`が消え、`=>`で引数と関数本体をつなぎます。

```js
const square = (num) => {
  return num ** 2;
};
```

アロー関数は引数が一つだけの場合、`()` を省略できます。

```js
const square = num => {
  return num ** 2;
};
```

関数本体が１文の場合、`{}` を省略でます。
その場合、関数本体が戻り値になるので`return`も不要です。

```js
const square = num => num ** 2;
```

## 前置き②：高階関数

JavaScript の関数は第一級オブジェクトであるため、関数を引数や戻り値にできます。

```js:配列の条件に一致する要素の数を返す関数
/**
 * 配列の条件に一致する要素の数を返す
 * @param {Array} list - 配列
 * @param {function} judgeFunc - trueまたはfalseを返す判定関数
 */
function count(list = [], judgeFunc) {
  let num = 0;
  for (const item of list) {
    if (judgeFunc(item)) {
      num++;
    }
  }
  return num;
}

// ランダムな数値の入った配列
const myList = [21, 48, 18, 1, 18, 37, 39, 2, 40, 14, 36, 6, 43, 17, 45, 34, 38, 21, 30, 12];

// ７で割り切れるかどうか
function myJudge1(num) {
  return num % 7 === 0;
}

// ３で割り切れるかどうか
function myJudge2(num) {
  return num % 3 === 0;
}

console.log(count(myList, myJudge1));  // 3
// ※関数そのものを渡しているので myJudge に () は付かない

console.log(count(myList, myJudge2));  // 11
```

関数を引数とするので柔軟に処理を切り替えられます。
上記のコードでは渡す関数を変えれば、素数を数えたり、5以下を数えたりできます。


引数に渡す関数を一回一回定義するのは冗長ですので、、`count`の呼び出し時、匿名関数として直接渡してみます。

```js
const result = count(myList, function(num) {
  return num % 7 == 0;
});

console.log(result);  // 3
```

匿名関数はアロー関数にすると簡潔です。

```js
const result = count(myList, num => num % 7 === 0);

console.log(result);  // 3
```

## 対象のデータ
ここから`Array`の`map`や`reduce`などの内容です。
サンプルデータとしてWeb API などから以下の製品一覧データをフェッチしたとします。

```js
const list = [
  {
    id: 1,
    name: "foo",
    active: true,
    price: 500,
    categories: ["a", "b", "c"]
  },
  {
    id: 2,
    name: "bar",
    active: false,
    price: 1000,
    categories: ["a", "e"]
  },
  {
    id: 3,
    name: "baz",
    active: true,
    price: 1500,
    categories: ["c", "e"]
  }
];
```

## `map` 

- [Array.prototype.map() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map)

対象の配列からあたらしい配列を作ります。

```js:名前だけの配列を作る
const names = list.map(p => p.name);

console.log(names);
/*
  ["foo", "bar", "baz"]
*/
```

```js:オブジェクトの配列を作る
const items = list.map(p => ({
  name: p.name,
  taxIncludePrice: p.price * 1.08
}));

console.log(items);
/*
  [
    {name: "foo", taxIncludePrice: 540},
    {name: "bar", taxIncludePrice: 1080},
    {name: "baz", taxIncludePrice: 1620}
  ]
*/
```

`map`に限りませんがアロー関数で匿名オブジェクトを返す場合、`{}`を`()`で囲みます。
`()`が無いと`{}`がアロー関数本体の括弧と見做されてしまいます。

```js:最大値を求める
const maxPrice = Math.max(...list.map(p => p.price));

console.log(maxPrice);
// 1500
```

`list.map()` で作られた価格の配列を`...`（スプレッド構文）で展開すると、`Math.max`により最大値が得られます。

## `filter`

- [Array.prototype.filter() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)

配列から条件に一致した要素のみを抽出し新しく配列を作ります。

```js:有効製品のみ抽出する
const activeProducts = list.filter(p => p.active);

console.log(activeProducts);
/*
  [
    {id: 1, name: "foo", active: true, price: 500, categories: ["a", "e"]},
    {id: 3, name: "baz", active: true, price: 1500, categories: ["c", "e"]}
  ]
*/
```

```js:1000円を超える製品を抽出する
const over1000YenProducts = list.filter(p => p.price >= 1000);

console.log(over1000YenProducts);
/*
  [
    {id: 2, name: "bar", active: false, price: 1000, categories: ["a", "e"]},
    {id: 3, name: "baz", active: true, price: 1500, categories: ["c", "e"]}
  ]
*/
```

## `find`

- [Array.prototype.find() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find)

条件に一致した要素を一つ取り出します。
複数ある場合は最初にマッチした要素を返します。
見つからない場合、戻り値は`undefind`です。

```js:idが2の製品を取得する
const product = list.find(p => p.id === 2);

console.log(product);
// {id: 2, name: "bar", active: false, price: 1000, categories: ["a", "c"]}
```

## `every`

- [Array.prototype.every() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/every)

全ての要素が条件を満たすかを真偽値で返します。

```js:すべての製品の価格が１０００円以下か？
const all1000YenOrLess = list.every(p => p.price <= 1000);

console.log(all1000YenOrLess);
// false
```

## `some`

- [Array.prototype.some() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/some)

条件を満たす要素が含まれるかを真偽値で返します。

```js:カテゴリcを含む製品が存在するか？
const exists = list.some(p => p.categories.includes("c"));

console.log(exists);
// true
```

```js:税込み１６００円以上の製品が存在するか
const exists = list.some(p => p.price * 1.08 >= 1600);

console.log(exists)
// ture
```

## `reduce`

- [Array.prototype.reduce() - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) 

配列を一つの値に畳み込みます。
動きが分かりずらいのでデバッグでステップ実行すると理解しやすいです。
リンク先の`How reduce() works`が分かりやすいです。


```js:合計金額を求める
const summary = list
  .map(p => p.price)
  .reduce((acc, cur) => acc + cur);

console.log(summary);
// 3000

// 平均金額を求める
const average = summary / list.length;
console.log(average);
// 1000
```

```js:配列の入れ子を平坦化する
const categories = list
  .map(p => p.categories)
  .reduce((acc, cur) => [...acc, ...cur]);

console.log(categories);
// ["a", "b", "c", "a", "c", "a", "e"]

// 重複を除く
const distinct= list
  .map(p => p.categories)
  .reduce((acc, cur) => [...acc, ...cur])
  .filter((c, i, self) => self.indexOf(c) === i);
// ["a", "b", "c", "e"]
```

## `sort`

```js:値段で昇順ソート
const sorted = [...list].sort((a, b) => a.price - b.price);

console.log(sorted);
/*
  [
    {id: 1, name: "foo", active: true, price: 500, categories: ["a", "b", "c"]},
    {id: 2, name: "bar", active: false, price: 1000, categories: ["a", "c"]}
    {id: 3, name: "baz", active: true, price: 1500, categories: ["a", "e"]}
*/
```

`[...list]`で配列をコピーしています。（要素の参照は保たれます）
`list.sort`すると元の配列がソートされてしまうため注意が必要です。
降順ソートしたい場合は`b.price - a.price`にします。

```js:複数キーでソートする
const multipleSorted = Array.of(...list).sort(
  (a, b) => a.price - b.price || a.id - b.id
);
```

JavaScriptでは`0`は`falsy`な値とみなされるため、`a.price - b.price`が`0`の場合、`||`により`a.id - b.id` が評価されます。
`[...list]`と`Array.of(...list)`は同じです。

## まとめ

`for`と違い、書き手の意図を読み取りやすいのが`map`とか`filter`のいいところだと思います。
ほとんどの場合、`for`文よりスマートに書けますが、メソッドチェーン（`list.map().filter()....`）するたびに中間配列が作られます。
メモリにシビアな環境では`for`のほうが良いようです。

`groupBy`やらも`reduce`で頑張ればできますが、JavaScriptは配列操作に弱いので素直にライブラリ頼ったほうが良さそうです。

- *Lodash*：https://lodash.com/
    - おなじみの使いやすい配列操作ライブラリです。
- *lazy.js*： http://danieltao.com/lazy.js/
   - Lodashっぽいですが、遅延評価です。
- *linq.js*： https://github.com/neuecc/linq.js/
    - C# の言語統合クエリの js の実装。
    - LINQ 同様に遅延評価です。
- *Rx.js*： http://reactivex.io/rxjs/
   - 今波が来てるリアクティブ・プログラミング用のライブラリです。
   - 非同期処理をイイ感じ書けます。
   - Rx.NETとかRxGoとかRxPYとかRx.rbとかRxSwiftとかreaxiveとかいろいろあります。