JavaScriptで競プロをする
標準入力からいきなり苦戦したのでメモ
JavaScript で競プロをやる
ふと、JavaScript で競プロをやってみようと思って調べたことのメモです。
なぜ JavaScript なのか
単純に JavaScript で簡単な問題をノックして、基本処理がスラスラと書ける状態にしたいというのが理由です。
また、興味深い内容として、どうも C++よりは遅いものの今の JavaScript は JIT コンパイルされるのでPython や Ruby に比べれば高速だという情報を見ました。
とはいえ、
function
だらけの黒魔術のようなコードとスクリプト言語特有の遅さは、ここ数年の JavaScript の目覚ましい進化によって克服されてきています。 現在の JavaScript 処理系は JIT コンパイルが主流のため、C++や Java のような言語には敵わないとはいえ、Python や Ruby のようなスクリプト言語よりも圧倒的に高速です。 また ECMAScript2015 の登場により、もはやfunction
と書く必要はなくなりました。
引用元は2018年に投稿された記事のようですね。
まあ、JIT コンパイルの恩恵を受けたいだけならPyPyを使えばいいですし、そもそも実行速度を求めるならC++とかRustとか使おうねっていう話になるのであまり追求はしません。
JITコンパイルとは
このあたりの内部的な詳しいことをまだちゃんと理解できるレベルに自分が到達できていないのですが、現時点での理解を書いておきます。
- C++などの AOT コンパイル(Ahead-Of-Time: 事前コンパイル)言語が速いのは、ソースコードを事前にコンパイル(全体に渡って処理を最適化)して機械語に翻訳しているのが理由
- Python や Ruby などのインタプリタ形式の(対話型コンソールで実行できるような)言語が遅いのは、ソースコードを 1 行ごとに都度解釈しているのが理由で、なるほど全体を見通して最適化するコンパイル言語より実行は遅くなる
- JIT コンパイル(Just-In-Time: 実行時コンパイル)では、インタプリタが行ごとに毎回解釈し直すのに対し、あくまで実行時コンパイルのため、部分ごとのコンパイルにはなるものの、重複した処理の解釈し直しなどが減り、高速になる
- JIT コンパイルは、AOT コンパイルと比較してソース全域の最適化には弱いものの、場合によっては AOT より高速になるケースもあるらしい(よくわかっていない)
イメージとして、外国に行くことを考えると分かりやすいかも知れません。
- 事前に行く国を決めてその国の言葉を勉強するのが AOT コンパイル(あとから言語の変更不可だが即適応する)
- とりあえずその国に着いてからその国の言葉を急いで習得するのが JIT コンパイル(言語変更可能だがオーバーヘッドがある)
- 話すときは常に通訳を使ってしまうのがトランスパイル形式(言語変更可能だが同じ会話内容でも毎回通訳が必要になる)
JavaScript での標準入出力
閑話休題。今回の本題はこれです。
入力
結論からいくと、以下が FA です。
const input = require('fs').readFileSync('/dev/stdin', 'utf8');
これで入力全行の文字列がinput
に入ります。
こっから各行を取り出すならinput.split('\n')
すれば OK ですし、最初のinput
への代入の時点でsplit
してしまっても大丈夫。
ネットを見てると、AtCoder だとimport * as fs from 'fs';
も使えるという情報があるんですが、どうも paiza だとimport
でエラーを吐くのでrequire
を使っておくのがよさそうです。
他にもreadline
を使用する方法もあるっぽいですが、こっちはかなり煩雑なのでやめたほうがいいと思います。
ぶっちゃけよくわかってません。
出力
これはおなじみのconsole.log
を使っておけば万事 OK です。
半角スペース区切りで出力したいときは普通にカンマ区切りで OK みたいですね。
console.log(1, 2, 3, 4); // 1 2 3 4
タグ付きテンプレートリテラル
おまけ情報ですが、テンプレートリテラルに特殊な使い方があるというのを調べてる過程で学びました。
// 普通はこう書く
const firstLine = input.split('\n')[0];
// タグ付きテンプレートリテラル
const firstLine = input.split`\n`[0];
// 複数引数の場合
function sampleFunc(arg1, arg2, arg3) {
console.log(arg1);
console.log(arg2);
console.log(arg3);
}
sampleFunc`hoge${'fuga'}piyo${100}`;
// ['hoge', 'piyo','']
// 'fuga'
// 100
こんな感じで、${}
区切りの文字列配列が第 1 引数に、それ以外は${}
の中身が引数に割り当てられるようですね。
意外だったのは、最後の${}
のあとも空文字列として渡されているとこ ろでしょうか。
うーん、JavaScript もなかなか他の言語で見慣れない処理がありますね。 まだ prototype とかの概念もちゃんとわかっていないので、これを期に勉強したいと思います。