社内で2回目のTOPSICをやるので復習がてらJavaScriptのノートを残しておきます。
前回は70点だったので100点取りたい。
JavaScriptを選んだ理由
一番自信がある言語を選びました。
「腕試しがてら○○で挑戦してダメだった!笑」みたいな声を周りで聞きました。
TOPSIC導入の意図はアルゴリズム力の測定だと思うので、素直に得意言語にしましょう。
それ以外では、1行コードを確認するにはChromeの開発者ツールが使えるので楽!
JavaScriptを選ぶ際の注意点
TOPSICのNode.jsの環境がv12系(2019年リリース)で若干古いです。
古いことにより、以下の問題が考えられます。
- 搭載しているv8エンジンが古いため実行速度が遅い
- ECMAScriptの新しめの言語仕様が使えないことがある
TOPSICはコード実行制限があり、これをオーバーすると正解でもNGになります。
Node.jsのエンジン、v8の実行速度は改善されていますが、JavaやGoと比べると不利です。
といいつつほとんど問題になることは無いですが、私は練習問題で1問だけこの問題でNGになりました。
受験対象テストのレベルより難しいもの(★4以上)だったら影響が出てくるのかも。
言語仕様の問題としては、練習問題答えている時に以下のような分割代入でエラーがでました。
// 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
フローを考える
フローチャートを手元で書いてもいいですし、シンプルなものならコメント書いていくでも良いですね。
複雑な問題に置いては、問題をいかに簡単な手順の集合に分解できるかが鍵です。
// 入力データを行ごとに分割し変数にする
// 2行目のデータをスペースで分割し変数b,cにする
// a+b+cの合計sumを出す
// sumとsを指定の形で出力する
入力はStringなので、String.prototype.split()
メソッドを使い行ごとに分割したデータにします。
今回は分割代入を使ってコードを簡略化します。参考:MDN 分割代入
// 入力データを行ごとに分割し変数にする
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}`)
分割代入や高階関数を使わない場合は以下の感じになります。
// 入力データを行ごとに分割し変数にする
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()
を駆使してデータを良い感じに整形しましょう。
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のリファレンスを見るのオススメです。
map
配列の各要素ごとに関数を呼び出し、その戻り値からなる新たな配列を返します。
返される配列の長さは元の配列から変わりません。
const inputArray = [1, 2, 3, 4, 5]
const doubledArray = inputArray.map(num=> {
return num * 2
})
// doubledArray: [2, 4, 6, 8, 10]
reduce
配列の各要素関数ごとに呼び出し、直前の関数実行の返り値を渡し、単一の値を返します。
合計とか新たなオブジェクトを作りたいときなどに利用することが多いです。
const inputArray = [1, 2, 3, 4, 5]
const sum = inputArray.reduce((prevNum, currentNum) => {
return prevNum + currentNum
}, 0) // 0は初期値
// sum: 15
filter
配列の各要素ごとにテスト関数を呼び出し、テスト関数を満たす値からなる新たな配列を返します。
const inputArray = [1, 2, 3, 4, 5]
const threeOrGreaterArray = inputArray.filter(num => {
return num >= 3
})
// threeOrGreaterArray: [3, 4, 5]
forEach
配列の各要素ごとに関数を呼び出し、実行します。undefinedを返します。
配列の値ごとにconsole.log()
で出力したいときとかに重宝します。
const inputArray = [1, 2, 3, 4, 5]
inputArray.forEach(num => console.log(num))
// 出力結果↓↓
// 1
// 2
// 3
// 4
// 5
ローカルでコードを書き、動作確認できるようにしておくこと
TOPSICのエディタは入力補完も何もないです。
関数の細かいスペルを覚えておくことがTOPSICの意図ではないので、
自分の得意言語を使い慣れたエディターで書いてすぐ動作確認できるようにしましょう。
練習問題でTOPSICの各言語のバージョンが確認できるので、念を入れるなら合わせると良いです。
私は以下のコードテンプレートを元に、VSCode上で動作確認できるようにしています。
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のページをリファレンスとしてすぐ使えるよう別タブで開いておくのオススメです。
ロジックをブレークダウンする
ここが結局大事で、情報をシンプルな問題の集合にすることで複雑な問題を解決します。
が、このブレークダウンしたり問題をどう捉えるかみたいなのの言語化が難しい。
今回はどうだった?
100/100点でした!やったね!