programming

JavaScriptで競プロをする

標準入力からいきなり苦戦したのでメモ

You can share this article with

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 とかの概念もちゃんとわかっていないので、これを期に勉強したいと思います。