JavaScriptでTOPSICする時用のノート

社内で2回目のTOPSICをやるので復習がてらJavaScriptのノートを残しておきます。
前回は70点だったので100点取りたい。

### JavaScriptを選んだ理由

一番自信がある言語を選びました。
「腕試しがてら○○で挑戦してダメだった！笑」みたいな声を周りで聞きました。
TOPSIC導入の意図はアルゴリズム力の測定だと思うので、素直に得意言語にしましょう。

それ以外では、1行コードを確認するにはChromeの開発者ツールが使えるので楽！

### JavaScriptを選ぶ際の注意点

TOPSICのNode.jsの環境がv12系(2019年リリース)で若干古いです。
古いことにより、以下の問題が考えられます。

- 搭載しているv8エンジンが古いため実行速度が遅い
- ECMAScriptの新しめの言語仕様が使えないことがある

TOPSICはコード実行制限があり、これをオーバーすると正解でもNGになります。
Node.jsのエンジン、v8の実行速度は改善されていますが、[JavaやGoと比べると不利です](http://www.hykn.org/hnb/2018/02/go-python-c-node-js-java%E3%81%AE%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9/)。
といいつつほとんど問題になることは無いですが、私は練習問題で1問だけこの問題でNGになりました。
受験対象テストのレベルより難しいもの(★4以上)だったら影響が出てくるのかも。

言語仕様の問題としては、練習問題答えている時に以下のような分割代入でエラーがでました。

```javascript
// NG例
let [a, b, c] = ['1', '2', '3']
[a, b, c] = [a, b, c].map(numStr => parseInt(numStr, 10))

// OK例
const [a, b, c] = ['1', '2', '3']
const [aNum, bNum, cNum] =  [a, b, c].map(numStr => parseInt(numStr, 10))
```

実行すると`ReferenceError: Cannot access 'b' before initialization`となります。
ちなみに自分の環境のNode v16系ではNG例も問題なく動作します。なんで。

**追記：** v17で試したらダメだった。なんで。私の理解が間違ってそう。

それ以外で気をつけるべきポイントは、配列操作のメソッドがmutableかimmutableか知ることです。
mutableは元の配列を変えてしまうメソッド、immutableは元の配列を変えないメソッドです。


## 練習問題での解き方をなぞる

### 問題と入出力のチェック

練習問題は以下の入力の時に、`a + b + c`と`s`を空白区切りで1行に表示せよというものでした。

```
a
b c
s
```

以下の入力例だと、`6 test`が答えになります。

```
1
2 3
test
```

### フローを考える

フローチャートを手元で書いてもいいですし、シンプルなものならコメント書いていくでも良いですね。
複雑な問題に置いては、問題をいかに簡単な手順の集合に分解できるかが鍵です。

```javascript
// 入力データを行ごとに分割し変数にする

// 2行目のデータをスペースで分割し変数b,cにする

// a+b+cの合計sumを出す

// sumとsを指定の形で出力する
```

入力はStringなので、`String.prototype.split()`メソッドを使い行ごとに分割したデータにします。
今回は分割代入を使ってコードを簡略化します。[参考：MDN 分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)

```javascript
// 入力データを行ごとに分割し変数にする
const [a, bc, s] = input.split('\n') // 改行(\n)で分割し、aが'1'、bcが'2 3'、sが'test'になる

// 2行目のデータをスペースで分割し変数b,cにする
const [b, c] = bc.split(' ') // bcの'2 3'をスペースで分割しb,cに代入する

// a+b+cの合計sumを出す
const sum = [a, b, c].reduce((sum, curNumStr) => sum + parseInt(curNumStr, 10), 0)

// sumとsを指定の形で出力する
console.log(`${sum} ${s}`)
```

分割代入や高階関数を使わない場合は以下の感じになります。

```javascript
// 入力データを行ごとに分割し変数にする
const inputArray = input.split('\n') // inputArrayは ['1', '2 3', 'test']になる
const a = parseInt(inputArray[0], 10) // inputArrayの1つめ(index: 0)の要素を10進数で数値にする
const s = inputArray[2]

// 2行目のデータをスペースで分割し変数b,cにする
const tmpArray = inputArray[1].split(' ')
const b = parseInt(tmpArray[0], 10)
const c = parseInt(tmpArray[1], 10)

// a+b+cの合計sumを出す
const sum = a + b + c

// sumとsを指定の形で出力する
console.log(`${sum} ${s}`)
```

分割代入や高階関数を使うことでコード量も減り、見通しが良くなります。
`map` `filter` `reduce`などは`for`に比べてコードの意図が分かりやすくなります。
TOPSICには関係ないですがコード行数で生産性を測れないことが解りますね！

## 解いていく上でのポイント

いくつか問題を解く上で知っておくと良いことをまとめます。

### 入力データのハンドリング方法を知っておくこと

入力データは改行区切りやスペース区切りやカンマ区切りで出ます。
そして入力データは`String`文字列で送られることを忘れないことです。
`split()`と`parseInt()`を駆使してデータを良い感じに整形しましょう。

```javascript
1 2 3
4 5 6
7 8 9

function Main(input) {
  const inputArray = input.split('\n') // 改行区切り
  // inputArray = ['1 2 3', '4 5 6', '7 8 9']
  
  const array1 = inputArray[0].split(' ') // スペース区切り
  // array1 = ['1', '2', '3']

  const numArray1 = array1.map(numStr => parseInt(numStr, 10)) // 数字の文字列を数値に変換
  // numArray1 = [1, 2, 3]

  const twoDimensionalNumArray = inputArray.map(element => {
    return element.split(' ').map(numStr => parseInt(numStr, 10))
  })
  // twoDimensionalNumArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

  const flatNumArray = twoDimensionalNumArray.flat()
  // flatNumArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]

  const sum = flatNumArray.reduce((prev, cur) => prev + cur, 0)
  // sum = 45
}
```

入力を良い感じに扱いやすいデータにする方法を知っておくことで時間短縮ができます。

### 主要な高階関数をいくつか把握しておく

アルゴリズム問題解く時に使い所が多い高階関数は以下です：

- map
- reduce
- filter
- forEach

以下で簡単に説明しますが、[MDNのリファレンス](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)を見るのオススメです。

#### map

配列の各要素ごとに関数を呼び出し、その戻り値からなる新たな配列を返します。
返される配列の長さは元の配列から変わりません。

```javascript
const inputArray = [1, 2, 3, 4, 5]
const doubledArray = inputArray.map(num=> {
  return num * 2
})
// doubledArray: [2, 4, 6, 8, 10]
```

#### reduce

配列の各要素関数ごとに呼び出し、直前の関数実行の返り値を渡し、単一の値を返します。
合計とか新たなオブジェクトを作りたいときなどに利用することが多いです。

```javascript
const inputArray = [1, 2, 3, 4, 5]
const sum = inputArray.reduce((prevNum, currentNum) => {
  return prevNum + currentNum
}, 0) // 0は初期値
// sum: 15
```

#### filter

配列の各要素ごとにテスト関数を呼び出し、テスト関数を満たす値からなる新たな配列を返します。

```javascript
const inputArray = [1, 2, 3, 4, 5]
const threeOrGreaterArray = inputArray.filter(num => {
  return num >= 3
})
// threeOrGreaterArray: [3, 4, 5]
```

#### forEach

配列の各要素ごとに関数を呼び出し、実行します。undefinedを返します。
配列の値ごとに`console.log()`で出力したいときとかに重宝します。

```javascript
const inputArray = [1, 2, 3, 4, 5]
inputArray.forEach(num => console.log(num))
// 出力結果↓↓
// 1
// 2
// 3
// 4
// 5
```

### ローカルでコードを書き、動作確認できるようにしておくこと

TOPSICのエディタは入力補完も何もないです。
関数の細かいスペルを覚えておくことがTOPSICの意図ではないので、
自分の得意言語を使い慣れたエディターで書いてすぐ動作確認できるようにしましょう。
練習問題でTOPSICの各言語のバージョンが確認できるので、念を入れるなら合わせると良いです。

私は以下のコードテンプレートを元に、VSCode上で動作確認できるようにしています。

```javascript:topsic.js
const input = 
`1 2 3
4 5 6
7 8 9
` //ここに問題の入力例のテキストをコピペします

function Main(input) {
  // 実装はここ
  console.log()
}

Main(input)
//*この行以降は編集しないでください（標準入出力から一度に読み込み、Mainを呼び出します）
// Main(require("fs").readFileSync("/dev/stdin", "utf8"));
``` 

Node.jsをインストールしていればローカル上で`node topsic.js`を実行すれば動作確認できます。
動いたらTOPSICの解答欄にコピーし、コード上の`Main(input)`を削除して、
最後の行ののコメントアウトを外し、TOPSIC上でも入力例のパターンで動作確認してから提出してます。

動作確認の時の入力データを使う時は必ずTOPSIC上のCopyボタンを使いましょう。
以前、選択コピーしてやったら最後の改行コピーしておらず間違えたことがありました。

### MDNのドキュメント見れるようにしておく

私自身いまだに`splice`と`slice`ってどっちをどう使うんだっけ？とか、
上で紹介したような高階関数の引数の使い方はよくわからなくなります。
こういうアルゴリズム系のプログラミングは配列をよく使うので、
[MDN Arrayのページ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)をリファレンスとしてすぐ使えるよう別タブで開いておくのオススメです。

### ロジックをブレークダウンする

ここが結局大事で、情報をシンプルな問題の集合にすることで複雑な問題を解決します。
が、このブレークダウンしたり問題をどう捉えるかみたいなのの言語化が難しい。


## 今回はどうだった？

100/100点でした！やったね！