JSでWelcome to AtCoderを関数型っぽくイミュータブルに記述してみた

最初はnodeで提出しようとしたがatcoderのnodeがes2016の記法に対応してなかったためtsで提出した。tsならes2016に対応しているしjsのスーパーセットでもあるのでスピード重視で型定義などしなくてもes2016で書いてあげるとうまくテストをパスできる。

## 問題文

該当の問題はこちら
https://atcoder.jp/contests/practice/tasks/practice_1

ユーザー登録してログインすればアクセスが可能になる。

## まずは解答全文

```js:main.js
var flatter = function (accum, n, arr) {
    switch (n) {
        case arr.length:
            return accum;
        default:
            var nextAccum = accum.concat(arr[n]);
            return flatter(nextAccum, n + 1, arr);
    }
};
var accumulator = function (accum, n, arr) {
    switch (n) {
        case arr.length:
            return accum;
        default:
            var nextAccum = void 0;
            if (typeof arr[n] == "number") {
                nextAccum = [accum[0] + arr[n]];
            }
            else {
                nextAccum = accum.concat(arr[n]);
            }
            return accumulator(nextAccum, n + 1, arr);
    }
};
var Main = function (input) {
    var splitArr = input
        .split("\n")
        .map(function (e) { return e.split(" "); });
    var flattenArr = flatter([], 0, splitArr);
    var parsedArr = flattenArr.map(function (e) {
        if (parseInt(e, 10)) {
            return parseInt(e, 10);
        }
        else {
            return e;
        }
    });
    var accum = accumulator([0], 0, parsedArr);
    console.log(accum[0] + " " + accum[1]);
};
var input = require("fs").readFileSync("/dev/stdin", "utf8");
Main(input);

```

## flatter関数

```js:
var flatter = function (accum, n, arr) {
    switch (n) {
        case arr.length: // n番目まで繰り返した場合
            return accum; // アキュムレータを返す
        default:
            var nextAccum = accum.concat(arr[n]);
            return flatter(nextAccum, n + 1, arr);
    }
};
```

配列を平坦化する関数を定義する。関数型言語においてイテレーションを実装するとなると引数にアキュムレータ（情報を保持するために追加された引数）、カウンタ変数、対象となるデータ構造をもたせることが多い印象。

末尾はスタックを積みすぎてしまわないように末尾呼び出し最適化が適用できるように末尾呼び出しにしてやる。末尾呼び出し最適化に関しては以下の記事に詳しい。

[末尾再帰による最適化#末尾呼び出しと末尾呼び出し最適化 | Qiita](https://qiita.com/pebblip/items/cf8d3230969b2f6b3132#%E6%9C%AB%E5%B0%BE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A8%E6%9C%AB%E5%B0%BE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E6%9C%80%E9%81%A9%E5%8C%96)

## accumulator関数

```js:
var accumulator = function (accum, n, arr) {
    switch (n) {
        case arr.length: // n番目まで繰り返した場合
            return accum; // アキュムレータが返る
        default:
            var nextAccum = void 0; // undefinedが格納される
            if (typeof arr[n] == "number") { // もしも数字なら
                nextAccum = [accum[0] + arr[n]]; // 配列の最初の要素に足し合わせる
            }
            else {
                // 数字でなければアキュムレータに追加して新しいアキュムレータを返す
                nextAccum = accum.concat(arr[n]); 
            }
            return accumulator(nextAccum, n + 1, arr);
    }
};
```

配列の中身の数字だけを足し合わせる関数を定義する。

2分岐だとif文case文共にパフォーマンスは変わらないみたいだけど多分岐になるとcase文の方がパフォーマンスはいいとされている模様

[Javascript 「if文」と「switch文」の処理速度を調べる | Qiita](https://qiita.com/ko8@github/items/f9aaf17c9c519386a961)

## Main関数

```js:
var Main = function (input) {
    // splitArrに標準入力の文字列を段落とスペース区切りで配列化して格納
    var splitArr = input
        .split("\n")
        .map(function (e) { return e.split(" "); });
    var flattenArr = flatter([], 0, splitArr); // 配列をならす
    // もしsplitArrの各要素が10進数に変換できるならする
    var parsedArr = flattenArr.map(function (e) {
        if (parseInt(e, 10)) {
            return parseInt(e, 10);
        }
        else {
            return e;
        }
    });
    var accum = accumulator([0], 0, parsedArr); // 数字だけを足し合わせる
    console.log(accum[0] + " " + accum[1]); // 計算結果を標準出力
};
```

## 呼び出し部分

```js:
var input = require("fs").readFileSync("/dev/stdin", "utf8"); // 標準入力を受け取る
Main(input);
```

実際にコマンドラインから試す場合にはcatの標準出力をパイプで標準入力として受け取ってやれば良い。


```txt:input.txt
1
2 3
test
```

```shell:
$ cat test.txt | node main.js
# -> 6 test
```


## 公式の解答例との比較

```js:公式のnode.jsでの解答例
// inputに入力データ全体が入る
var Main = function (input) {
	// 1行目がinput[0], 2行目がinput[1], …に入る
	input = input.split("\n");
	tmp = input[1].split(" ");
	//文字列から10進数に変換するときはparseIntを使います
	var a = parseInt(input[0], 10);
	var b = parseInt(tmp[0], 10);
	var c = parseInt(tmp[1], 10);
	var s = input[2];
	//出力
	console.log('%d %s',a+b+c,s);
}
//*この行以降は編集しないでください（標準入出力から一度に読み込み、Mainを呼び出します）
Main(require("fs").readFileSync("/dev/stdin", "utf8"));
```

記述量はおよそ倍になったもののの、実行時間は関数型ちっくに書いた方が良かった。理由は今のところわかっていない

<img width="1089" alt="Screen Shot 2019-08-04 at 15.47.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170241/3f88d1c7-796a-1fe7-19f0-367ee6133461.png">


以上。