TypeScriptでAtCoder始めたいからツール作った
こんにちは!!
久々にAtCoderで競技プログラミングをしたいなとなったのですが、個人的にTypeScriptで参加しづらく感じたので、コードエディタ上(自分はVSCode)で動かすことを想定したサポートツール作りました。
TypeScriptでAtCoderしているorしようと思っている方、良ければ参考にしていただけると幸いです。(ツールも使ってくれるとなおうれしいです。)
AtCoderにTypeScriptで参加する上で感じた"やりづらさ"について
これは自分が、C言語で競技プログラミングを始めたことにも起因するのですが、CLI上で実行~入力してテストまでできるのが当たり前だと思って生きてきました。
どういうことかというと、例えばAtCoderでの実際の練習問題を例にします。
問題には以下のような、テストデータとして使える入力例と出力例がいくつか書かれているわけです。
入力例 1
1
2 3
test
出力例 1
6 test
C言語であれば、コードを書いた後に入力例をコピーしてCLIに張り付ければそのまま実行して出力結果までパッと見れるわけです。
作成したコードを実行するところから結果確認までを1画面上で完結でき、自分の手に馴染んだ方法でした。
じゃあ、TypeScriptはどうでしょう。
先ほど提示した練習問題に対するTypeSciptでの回答例を以下として、検証してみます。
import * as fs from "fs";
const input = () => {
const stdin = fs.readFileSync(process.stdin.fd, "utf8");
return stdin.split("\n").map((v) => v.split(" "));
};
const output = (stdout: any) => {
console.log(stdout);
};
const main = () => {
const arr = input();
const a = Number(arr[0][0]);
const b = Number(arr[1][0]);
const c = Number(arr[1][1]);
const s = arr[2][0];
output(`${a + b + c} ${s}`);
};
main();
これをローカルのCLIで実行したときに、実はテスト結果を見ることができません。
なぜなら実際に実行するとわかるのですが、標準入力に対して永遠に待機するため、CLIからの実行だと入力ができても、結果を見ることはできないのです。
じゃあどのようにテスト結果を見るかというと、適当なテキストファイルに入力データを保存してあげて、スクリプトに渡してやる形になります。
なんてこった!画面1つで完結してたものが、適当なテキストファイルを経由しなきゃいけなくなってしまいました!
1手間増えただけなので、大した問題じゃないと感じるかもしれませんが、スピードと試行回数を大事にしている自分からすると死活問題です。コードのテストが増えるたびにストレスも増えていくのですから。
そこで、C言語のようにCLI上で結果まで見れるサポートツールを作りました。
開発したサポートツール
実際に開発したものをまとめたリポジトリは以下です。(一部記事用に修正している部分がございます。)
以降は、サポート機能の核となる部分をご紹介します。
標準入力の受付と受付完了判定
先ほど述べた通り、標準入力が無限に続いてしまう問題を解決すべく、「CLI上で入力された1行がただの改行だけ(ほかのテキスト入力がなし)」だったタイミングで入力を完了というルールにしました。(これまでの競技問題的にもその仕様でOKだったはず...)
よってコードとしては以下のように、改行のみが来るまで入力を受け付けて待機するように実装しています。
const stdin = async (): Promise<string> => {
return new Promise((resolve) => {
let lines: string[] = [];
const reader = readline.createInterface({
input: process.stdin,
});
reader.on("line", (line) => {
// 改行のみだったときに標準入力を終了したと判断
if (line == "") {
reader.close();
}
lines.push(line);
});
reader.on("close", () => {
resolve(lines.join("\n"));
});
});
};
サブプロセスでのテスト実行
テスト実行を行うスクリプトは以下のような構成となっています。
- コマンドラインから回答用ファイルへのパスを引数として受け取る。
- 標準入力からテスト入力データを受け取り、テキストファイルとしてローカルに一時保存する。
- サブプロセス上で、テキストファイルデータを使ってテストを実行する。
const STDIN_TMP_FILE = './input-tmp.txt'
const main = async () => {
const args = process.argv.slice(2);
const filePath = args.at(0);
const input = await stdin();
fs.writeFileSync(STDIN_TMP_FILE, input);
// 問題用スクリプトを実行
exec(
`npx tsx filePath < ${STDIN_TMP_FILE}`,
(err, stdout, stderr) => {
if (err) {
console.error(`error: ${stderr}`);
return;
}
console.log(stdout);
}
);
};
main();
これにより、テスト実行のためのラッパー的役割を持った関数ができたわけです。
CLI上からコマンドを実行
CLIからサポート用スクリプトを動かしてみます。サポート用のスクリプトへのパスを./main.ts
、提出用スクリプトへのパスを./a.ts
とすれば、以下の要領で実行できるはずです。
npx tsx ./main.ts ./a.ts
# (typescriptの実行には https://tsx.is/ ライブラリを活用しています)
↓ AtCoder側から入力データをコピーペースト
1
2 3
test
6 test
Done in 24.00s.
ここまでで、CLI上でテスト実行~結果確認まで簡単にできるようになりました!
自分の手に馴染んだC言語での競プロと同じような環境にできて満足です。
[+α] VSCode拡張機能のCodeRunnerでコマンドも省略しよう
ここからは番外編です。
さらに贅沢言えば、ターミナルでコマンド打つのも面倒くさいですよね。
1ステップで、今開いている提出用スクリプトを使って、勝手にテストしてくれたら最高じゃないですか?
それ、できるようにしましょう。
※ 説明するのはVSCodeでの実現方法になります。
CodeRunnerのインストール
VSCodeの拡張機能からCodeRunnerをインストールします。
このプラグインはざっくりいうと、お手軽コード実行機能です。
設定することで、特定のコマンドをすぐに実行できるようになります。
package.json
の設定
実行するファイルのパスなどが少し複雑になるなので、コマンドを実行しやすくしておきましょう。
`package.jsonのスクリプトとして、以下を記載します。
{
"scripts": {
"test": "npx tsx ./main.ts"
}
}
.vscode/settings.json
を作成
.vscode/settings.json
を作成して、CodeRunner用に
- ターミナル上での実行であること
- テスト用コマンドを実行すること
を以下のように設定しましょう。
{
"code-runner.runInTerminal": true,
"code-runner.executorMap": {
"typescript": "yarn test $fullFileName"
}
}
ショートカットキーを設定する
VSCodeの左下歯車ボタンから「キーボードショートカット」の設定を開きます。
検索欄に「CodeRunner」と入力すると「RunCode」という項目がヒットするはずです。
これを自分の使いやすいキーショートカットに変更しましょう。
(ちなみに自分の場合は Ctrl+Alt+S
にしています。Ctrl+S
でファイル保存した後に押しやすいかなと思いまして。)
ここまでくれば後は、対象のファイルを開いて設定したショートカットキーを押すとテストが動くはずです!
終わりに
いかがでしたでしょうか。
C言語でのやり方に慣れすぎた結果、ついカッとなって便利ツールを作ってみましたが、我ながら競プロ中の時間節約に寄与してくれるツールだと思っています。
もっと良い方法を知っていたり、実際に使ってみた中でこんな問題があった、などなど何かあったら教えてくれるとありがたいです。
また何か作ったら紹介しますね。ではでは!
Discussion