Node.js 競プロ 標準入力 メモ


Node.js の標準入力に取り憑かれています。

## 導入

以前も[似たような記事](https://qiita.com/cwd-k2/items/1d8f696161c901775dd4)を書いたのですが、ただの自作モジュールの紹介記事でした。

今回は「競プロっぽい」をテーマに自分が見てきた・考えてきたコード例を載せます。

自分が考えた例には★印を付けておきます。「信用しないでね」の意味です。

何か面白いパターンを思いついたら随時更新する予定です。

## `fs.readFileSync` を利用する例

こちらは変数に標準出力の内容を一度に受けてしまおうというものです。

### `split` して配列を保持する

```js
const inputs = require("fs").readFileSync("/dev/stdin", "utf-8").trim().split("\n");

// 1 行目は inputs[0]
// 2 行目は inputs[1]
// ...
```

今日日よく見られるものな気がします。

こちらは、N 行目を得るために `inputs[N-1]` をする必要があるのですが、**インデックスの管理が必要**になって面倒だなあという印象を受けます。

### ★ スキャナみたいなものを書いてみるといいかも

一度変数に受けたのだから、スキャナのようなものを書くといいんじゃないと思って考えてみました。現状次のものが一番楽に書ける気がします。

```js
const scan = {
  _buf: require("fs").readFileSync(0, "utf-8"),
  line() {
    const [s] = this._buf.split("\n", 1);
    this._buf = this._buf.substring(s.length + 1);
    return s;
  },
};
// scan.line() で一行読む
const n = Number(scan.line());
for (let i = 0; i < n; ++i) scan.line();
```

8 行必要になりますが、一度 `_buf` に文字列として標準入力を受けておいてどんどん消費していくようなものを書きました。ただし、改行文字が `\n` であることが前提です。

何度も `_buf.split(...)` を呼んでいて遅そうに見えるかもしれませんが、個人的に上のようにあらかじめ `split` して配列を持っておく例とそんなに速度的に変わらないような気がしています。

```js
for (let i = 0; i < n; ++i) {
  const str = scan.line();
  console.log(str);
  console.log(scan);
}
```

こんなふうに `scan` の内容を出力してみると `_buf` の内容がどんどん削れていく様子が見えて気持ちいいです。

#### 実行例

https://paiza.io/projects/5aLh_YP0NmpiD02tp32nyQ

## `readline` モジュールを利用する例

`readline` モジュールという一行ずつ読み取るためのものがあります。お誂え向きに見えますが、どちらかというと CLI で対話するためのものなのでややしんどい思いをすることになります。

### 一度入力が終わるまで行を全て読み取り、配列に保持する

こちらも競プロで標準入力を取る例としてよく見かけるものな気がします。

```js
const rl = require("readline").createInterface(process.stdin);

const inputs = [];

rl.on("line", (line) => {
  inputs.push(line);
});

rl.on("close", () => {
  // メイン処理...
});
```

イベント駆動だということで競プロでは見かけないようなコードをしています。

結局配列に全て受けるところは `fs.readFileSync` の初めの例と変わらないので、そこに気をつければ同じようにコーディングできる気がします。

`close` イベントを見て「入力が完了した」ということでメイン処理を開始する感じになっています。

### ★ `event` モジュールと組み合わせて「一行読む」関数を作る（`async/await`）

色々考えて作ってみました（楽しかった）。

```js
const { stdin, exit } = process;
const { log, error } = console;

const rl = require("events").on(require("readline").createInterface(stdin), "line");
const ln = () => rl.next().then(({ value: [l] }) => l);

async function main() {
  const [n, m, q] = (await ln()).split(" ").map(Number);
  const mat = [];
  for (let i = 0; i < n; ++i) mat.push((await ln()).split(" ").map(Number));
  // ...
}

main().then(() => exit(0)).catch(e => error(e) || exit(1));
```

ゴチャゴチャしていて見にくいかもしれませんが、`async function main` の中で `await ln()` とすればその場で一行読み出せる（ように見える）コーディングができるようになっています。

悩んで作った割にはそんなに良いコードではないのですが、「必要になったら待ち受ける」といった書き方ができるようになっています。そういうコーディングがしたい場面があればこちらの方が良いでしょう。

途中で入力が終わってしまった場合はどういうエラーが起こるんでしょうか。この辺よくわかっていません。

#### 実行例

https://paiza.io/projects/NeQs0Lbq7I02M4E6c9jIbg

## 2022/11/07 追記

### ★ `fs.readFileSync` の内容を分割して受け取る関数を `main` で利用する例

上の `fs.readFileSync` の例で、入力が必要ない段階で `scan` オブジェクトに `fs.readFileSync` を受けるのがなんとなく気に食わなくなって考え直していたのですが、以下の例を思いつきました。

```js
/** @arg {() => string} gets */
function main(gets) {
  console.log(gets().split(""));
  console.log(gets().split(""));
}

((_buffer) => main(() => {
  let [s] = _buffer.split("\n", 1);
  _buffer = _buffer.substring(s.length + 1);
  return s;
}))(require("fs").readFileSync(0, "utf-8")); // prettier-ignore
```

`main` 関数内でのみ引数の `gets` 関数を利用できるということで、`main` 実行直前に `readFileSync` してくれる感じです。関数名は他とぶつからないように好きなようにすればいいと思います（引数の名前を変えるだけ）。入力云々が必要なのは `main` だけということにすれば IO の切り分けもそれっぽいですし、ロジックが入力の上を走る感じでちょっと気に入っています。

`main` に渡す関数の中身が 3 行から縮まらないのが JS パワーが足りていないという感じですが、良いものがあればお教えください。

### ★ 上の `readline` 版

同様の方針で `readline` のものも書き直しました。

```js
/** @arg {() => Promise<string>} gets */
async function main(gets) {
  console.log((await gets()).split(""));
  console.log((await gets()).split(""));
}

// prettier-ignore
(i => main(() => i.next().then(({ value: [l] }) => l)).then(_ => process.exit(0)))
(require("events").on(require("readline").createInterface(process.stdin), "line"))
```

## 2022/11/14 追記

### ★ 「`fs.readFileSync` の内容を〜」を短くした

```js
/** @arg {() => string} gets */
function main(gets) {
  console.log(gets().split(""));
  console.log(gets().split(""));
}

// 3 行から雑に短くした
((b, s) => main(() => ([s] = b.split("\n", 1), b = b.substring(s.length + 1), s)))
(require("fs").readFileSync(0, "utf-8")); // prettier-ignore
```

多分お行儀は良くないんですが、ざっくり短くしてみました。

### ★　配列を取っておいてインデックス管理

「短くするだけなら配列・インデックス管理を押し込めただけの関数が楽なんでは？」ということに気がついて、当初の `fs.readFileSync` して `split` してインデックスを管理して... という方針に戻ったものです。

```js
/** @arg {() => string} gets */
function main(gets) {
  console.log(gets().split(""));
  console.log(gets().split(""));
}

((l, i) => main(() => l[i++]))
(require("fs").readFileSync(0, "utf-8").split("\n"), 0); // prettier-ignore
```

## 終わりに

競プロ環境に入ったら使いやすいんじゃないかなと思って[こういうの作った](https://qiita.com/cwd-k2/items/1d8f696161c901775dd4)んですけど、もちろん採用例はないです。

https://www.npmjs.com/package/node-gets

#### 実行例

https://paiza.io/projects/F4vQMcbKOeUQt4QfFExd7A