reduceの使い方メモ（主に集計目的）


reduceを集計目的で使うときのメモ。

## reduceの簡単な説明

mapとかfilterとかと同じ部類の関数ですが、他の関数に比べ（名前的にも）何してるのかわかりにくいですが、

```
[結果] = array.reduce([callback],初期値);
```

というような形式をとり（初期値は省略可能ですが、prevの意味を理解するためにも付けてたほうがいいです）、前の実行結果の影響を受ける（値を利用する）のが特徴です。

で、下記のように使います。

```
const array = [1,2,3];
const result = array.reduce((prev,current)=>prev + current,0);
```

reduceもmap等とどうようループします。で今回は配列が3つなので3回ループし、それぞれの処理として下記のような処理が実行されます。

* 1回目：prevの値は初期値の0、currentには1が入ります。なので、0 + 1 = 1が（次のprev値として）返ります。
* 2回目：prevの値は1、currentには2が入るので、 1 + 2 = 3が返ります。
* 3回目：prevの値は3、currentには3が入るので、 3 + 3 = 6が返ります。で、これが最終的な結果として返ります。

いかがでしょうか。

## 基本

### プリミティブ

合計、平均値、最大値、最小値とか。
平均値に関してだけは、sumを利用して別途計算するのがてっとり速いかと。

```js
const numbers = [1, 2, 3, 4, 5];

//合計
const sum = numbers.reduce((prev, current) => prev + current, 10);
console.log(sum);

//平均値
const average = sum / numbers.length;
console.log(average);

//最大値
const max = numbers.reduce((prev, current) => prev > current ? prev : current);
console.log(max);

//最小値
const min = numbers.reduce((prev, current) => prev < current ? prev : current);
console.log(min);


25
5
5
1
```

### オブジェクト

次にオブジェクト。実際の現場ではこちらのほうが一般的かと。
ま、対象とするkeyを指定するだけで、基本的にはプリミティブと同じ。
とりあえず、合計と最大値のサンプル（後は割愛）。

```js
//data
const members = [
    { name: "foo", score: 10, dep: 'sales1' },
    { name: "bar", score: 20, dep: 'sales2' },
    { name: "baz", score: 30, dep: 'sales2' },
]

//合計
const scoreSum = members.reduce((prev, current) => {
    return prev.score + current.score;
}, 0);
console.log(scoreSum);

//最大値
const maxScore = members.reduce((prev, current) => {
    return prev.score > current.score ? prev.score : current.score;
}, 0);
console.log(maxScore);


60
30
```

## 応用

### GroupBy的な

実開発ではよく使うのでメモ。
出力するデータ・フォーマットをイメージしておく必要があります。今回は、部署(dep)毎に下記のような形式でデータを出力する想定。

```
{
	dep:DEP_NAME, //部署名
	count COUNT, //何件あるか
	score: SCORE //スコアの合計
}
```

では作ってみます。

```js
//souce data
const members = [
    { name: "foo", score: 10, dep: 'sales1' },
    { name: "bar", score: 20, dep: 'sales2' },
    { name: "baz", score: 30, dep: 'sales2' },
]

//groupBy
const groupBy = members.reduce((result, current) => {
	
	//部署がprevにあるか
    const element = result.find(value => value.dep === current.dep);

    if (element) {
    	//ある時（下記、初期データを操作）
        element.count++;
        element.score += current.score;
    } else {
    	//無いとき（新規に初期データを作成）
        result.push({
            dep: current.dep,
            count: 1,
            score: current.score,
        })
    }
    return result;
    
}, []); //初期値は[]

console.log(groupBy);



[
  { dep: 'sales1', count: 1, score: 10 },
  { dep: 'sales2', count: 2, score: 50 }
]
```

### Average

平均を追記してみます。出力されたデータをmapでループして平均値を追加しています。

```js
const withAverage = groupBy.map(item => ({ ...item, ave: item.score / item.count }));
console.log(withAverage);

[
  { dep: 'sales1', count: 1, score: 10, ave: 10 },
  { dep: 'sales2', count: 2, score: 50, ave: 25 }
]
```

>平均には合計と合計カウントが必要なので1ループでは無理ですかね。。。mapをつなげて書くことはできるかと思います。

### Sort

ソート。まあ基本の応用。

```js
//sort
const sorted = withAverage.sort((prev, current) => prev.score < current.score ? 1 : -1); //大きい順
// const sorted = groupBy.sort((prev, current) => prev.score > current.score ? 1 : -1); //小さい順
console.log(sorted);


[
  { dep: 'sales2', count: 2, score: 50, ave: 25 },
  { dep: 'sales1', count: 1, score: 10, ave: 10 }
]
```

ひとまず以上です。必要に応じて随時追加予定。

## 番外編：配列データのJOIN（client side join)

集計の際によく２つの配列をJOINすることがあるのでついでに書いておきます。

```js
const deps = [
    { depId: 1, depName: "経理" },
    { depId: 2, depName: "営業" },
    { depId: 3, depName: "その他" },
];

const members = [
    { name: "foo", score: 10, depId: 1 },
    { name: "bar", score: 20, depId: 2 },
    { name: "baz", score: 30, depId: 3 },
]

const membersWithDep = members.map(member => {
	//既存のmemberの要素を展開しつつ、depIdがマッチしたdepNameの値を追記する
    return { ...member, depName: deps.find(value => value.depId === member.depId).depName }
});

console.log(membersWithDep);


[
  { name: 'foo', score: 10, depId: 1, depName: '経理' },
  { name: 'bar', score: 20, depId: 2, depName: '営業' },
  { name: 'baz', score: 30, depId: 3, depName: 'その他' }
]
```

## その他

[Firestoreでの適用事例](https://qiita.com/zaburo/items/d1e0c56ad56e895a8457)？を記述しました。