(Arrayの)反復メソッド活用：テーブルを集計しよう


> ※追記 2016/1/11　([@think49](http://qiita.com/think49)さんからご指摘)
> 
> 当記事はES6で追加の[Iterator Interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)ではなく、[Array.prototypeの方のIteration Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype)の話です。ご注意を。
> Iterator Interfaceを使うと当記事で試しているようなDOMを扱う操作も楽になりそうです。

JavascriptのArrayオブジェクトにビルトインされているイテレーション(反復)メソッドを使うと表からの情報取得や集計がスマートに書けるんじゃなかろうかと試してみました。

# イテレーション（反復）メソッドとは
配列操作用の関数の総称で、配列の全要素に対して何か操作をするだとか特定の条件の要素だけ取り出すとかのような、全要素にアクセスする処理を`[配列].xx(操作)`の形で書くような処理です。
代表的なものを挙げます。

## forEach
各要素に対して処理を行う。for文でやるようなことをきれいに書けます。
例：要素の中身全部表示

```js:forEach
[5,2,4,8].forEach(function(elem, index, array){
    console.log("No."+index+"+":"+elem);
});
// No.0:5
// No.1:2
// ・・・
```

## map
各要素に対して処理を行った結果を配列として返す処理です。
ポイントはforEachとは違って、配列を新しく返すところです。

よくある例として、数値を２倍にする例は以下のような感じ。

```js:map
[1,2,6].map(function(num){
    return num * 2;
});
//[2,4,12]
```

文字列を一括で数値に変換、とかが非常に楽です。

```js:mapで数値変換
["1","2","6"].map(Number);
//[1,2,6]
```

## some
各要素ごとに条件に合うか確認して、**条件を満たすものが１つでもある**とTrueを返す。

5より大きい数値があるかどうか確認の例。

```js:some
[1,2,6].some(function(num){return 5 < num});
//true
```
## every
各要素ごとに条件に合うか確認して、**すべての項目が条件を満たす**とTrueを返す。
必須項目の入力チェックとかに使えそうですね。

```js:every
[1,2,6].every(function(num){return 5 < num});
//false
```

## filter
各要素ごとに条件に合うか確認して、**条件に合う項目**を返す。

```js:filter
[1,2,6].filter(function(num){return 5 < num});
//[6]
```

## reduce
各要素を順に受け取り、１つの値にして返す。
これがなかなか使いどころのイメージが付きにくいです。

単純に合計値出す例

```js:reduce
[1,2,6].reduce(function(num1,num2){
    return num1 + num2;
});
// 9
```

今まで挙げたイテレーションメソッドと少し違い、引数に今までの処理結果と現在の値を取ります。

上記の例だと、
１回目の処理：
num1 : 1
num2 : 2
2回目の処理：
num1 : 3
num2 : 6

というように処理されます。
これを利用して、

```js:最小値を返す
[1,2,6].reduce(function(num1,num2){
    return (num1 < num2) ? num1 : num2;
});
```
というようなこともできます。Math.min()を使えと言われればそれまでですが。

こんなサンプルもありました。

```js:1次元配列にする
[[1,2],[2,3],[3,1]].reduce(function(arr1,arr2){
    return arr1.concat(arr2);
});
// 1,2,2,3,3,1
```

reduce単体での使い道はなかなか良いものが思い浮かびませんが、上記のmapやfilterと組み合わせて最後に集計するときに重宝しそうです。

## 参考
[MDN - Array](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)
[感謝のプログラミング 10000時間 - 配列操作を簡単にするJavaScriptのイテレーションメソッド一覧](http://blog.codebook-10000.com/entry/20140325/1395755647)

# テーブルを操作する
本題です。
イテレーションメソッドはArrayオブジェクトに定義されている関数です。
Array.prototype.xxx()
なのでArray型のものに対して有効なのですが、Javascriptではcallやapplyを利用することでイテレーションメソッドを持っていないオブジェクトも恩恵にあずかることができます。

## callを使うとArray型以外も操作できる

表を取得して全行に対してforEachを使い処理する例はこんな感じです。

```js:適用例
//テーブル取得
var tbl = document.getElementById("tbl");
//forEachを適用する
Array.prototype.forEach.call(tbl.rows,function(row){
    ///処理
});
```

実際に使えそうな例を考えつつ、イテレーションメソッドを使ってみます。

## 例１：数値によって色分けをしてみる
次のような表があったとします。
![image](https://qiita-image-store.s3.amazonaws.com/0/83530/b79702f7-01ba-5e58-fa73-aa8c81e753c0.png)
イメージとしては店ごとの値段調査…。

この表に対して、商品ごとに一番安いお店と高いお店をわかりやすくしたいと思います。
処理としては、行ごとに最大値と最小値を計算し、最大値のセルは赤色、最小値のセルは青色の文字にすることにします。

```js:例１
window.onload = function(){
    //表の取得
    var tbl = document.getElementById("tblPrice1");
    //sliceにより先頭行（ヘッダ行）を取り除く
    Array.prototype.slice.call(tbl.rows,1)
        //結果に対しての処理
        .forEach(function(rows){
            //ここは１行ごとの処理を記述
            //sliceにより先頭（商品名）を取り除く
            var vals = Array.prototype.slice.call(rows.cells, 1);
            //mapにより数値変換した結果のArrayを作る
            var nums = Array.prototype.map.call(vals,function(v){
                return Number(v.textContent)
            });
            //maxやminもapplyを利用すると便利
            var max = Math.max.apply(null,nums);
            var min = Math.min.apply(null,nums);
            
            //各要素で最大値か最小値が入っている場合の処理
            Array.prototype.forEach.call(vals,function(val){
                if(max == Number(val.textContent)){
                    val.style.color = "red";
                }
                if(min == Number(val.textContent)){
                    val.style.color = "blue";
                }
            });
    });
}
```
![image](https://qiita-image-store.s3.amazonaws.com/0/83530/d712fe03-5e7e-3cda-6640-a69610d1eb50.png)

こうなりました。

## 例２：条件によって行を非表示にする
次は、特定の数値がある行を消す操作をしてみます。
エラー値が含まれてるデータの除去とかをイメージしています。

![image](https://qiita-image-store.s3.amazonaws.com/0/83530/e47b41e6-66b5-a6b7-bb8a-5613ffe7a8fd.png)
-1が入った商品は非表示にしてみましょう。

```js:例２
window.onload = function(){
    var tbl = document.getElementById("tblPrice2");
   //先頭行を取り除くのは例１と一緒
    Array.prototype.slice.call(tbl.rows,1)
        .forEach(function(row){
            var doDel = Array.prototype.slice.call(row.cells, 1)
                //someを使うと一つでも条件を満たすかどうか判定できる
                .some(function(cell){
                    return (Number(cell.textContent) < 0);
                });
            //true判定されたものは非表示に
            if(doDel){row.style.display = "none";}
    });
}
```
![image](https://qiita-image-store.s3.amazonaws.com/0/83530/e394476c-1399-96e7-d942-c7a18922714c.png)

こうなりました。

## 例３：特定の条件を満たすものを抽出する
もう少し複雑な集計をしようとするとイテレーションメソッドの威力を発揮します。
次は、どの店でもいいので100円以下で売っているものの商品名を抽出してみます。
処理順としては
1. 行（商品）ごとに100以下の店があるか確認(some)
2. 100以下のあるお店の行だけ抽出(filter)
3. 抽出した行の先頭セルの文字列取得(map)
4. 取得した文字列を結合して表示(reduce)

![image](https://qiita-image-store.s3.amazonaws.com/0/83530/b1257565-fbf8-9954-e34c-d8c243bb128a.png)

```js:例３
window.onload = function(){
    var tbl = document.getElementById("tblPrice3");
    //ここは一緒
    var ret = Array.prototype.slice.call(tbl.rows,1)
    //条件を満たす行だけ取り出します
    .filter(function(row){
        return(Array.prototype.slice.call(row.cells, 1)
            //条件はここ。100未満の数値があればTrue
            .some(function(cell){
                return (Number(cell.textContent) < 100);
            })
        )
    })
    //抽出した行に対して、先頭セルの文字列のみ取り出した配列を作る
    .map(function(row){
        return row.cells[0].textContent;
    })
    //コンマをつけて結合
    .reduce(function(prev,curr){
        return prev + "," + curr;
    });
    //コンソールへ
    console.log(ret);
}

//出力↓
//鯛,お雑煮
```

# まとめ
イテレーションメソッドが有効なのは処理の記述量とかもありますが、例３のように「処理して→処理して→処理して」というように順に記述できることで処理順序がわかりやすくなる部分かと思います。
ただ、今回のようにテーブルを操作しようとするとtextContentを参照しないといけなかったりNumber変換が入ったりと、どうしても可視性が悪くなるところはありました。（これは書き方が下手なせいもあるが。）

うまく書くことで効率的な処理ができるよう引き続き勉強です。