JavaScriptで標準入力を扱うには？

## はじめに
JavaScriptで初めて競技プログラミングに挑戦したのですが、標準入力の値を取得するところで早速つまずきました。
サンプルコードがあったので試行錯誤して解決することはできましたが、
それぞれの処理の意味がよくわからなかったので、調べたことを備忘録として残しておきます。

## 標準入力・標準出力とは
### 標準入力
> 標準入力（standard input）とは、コンピュータ上で実行されているプログラムが、特に何も指定されていない場合に標準的に利用するデータ入力元。システム上では “stdin” の略号で表されることが多い。

(引用元) [標準入力（stdin）とは - 意味をわかりやすく - IT用語辞典 e-Words](https://e-words.jp/w/標準入力.html "標準入力（stdin）とは - 意味をわかりやすく - IT用語辞典 e-Words")

### 標準出力
> 標準出力（standard output）とは、コンピュータ上で実行されているプログラムが、特に何も指定されていない場合に標準的に利用するデータ出力先。システム上では “stdout” の略号で表されることが多い。

(引用元) [標準出力（stdout）とは - 意味をわかりやすく - IT用語辞典 e-Words](https://e-words.jp/w/標準出力.html "標準出力（stdout）とは - 意味をわかりやすく - IT用語辞典 e-Words")

## JavaScriptで標準入力を扱うには？
JavaScriptはWebブラウザ上で動く言語なので、他の一般的なプログラミング言語のように標準入力を扱う機能を持っていません。
そこでNode.jsという、JavaScriptの実行環境を用います。
Node.jsを用いるとJavaScriptをPC上で動かせるので、標準入力を扱うことができるようになります。
競技プログラミングのプラットフォームではNode.js環境を提供しているので、この記事で説明する方法が使えるわけです。

## 標準入力からデータを受け取る方法
ここでは、調べて出てきた2種類の方法について説明します。
- readlineを利用する方法
- /dev/stdinを利用する方法

### readlineを利用する方法
入力内容を1行ずつ処理する方法です。

```js:readlineを利用
process.stdin.resume();
process.stdin.setEncoding('utf8');

var lines = [];
var reader = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
});
reader.on('line', (line) => {
  lines.push(line);
});
reader.on('close', () => {
  console.log(lines[0]);
});
```
このコードは以下のようなことを行なっています。

#### 1. データ受信開始
```js
process.stdin.resume();
```
標準入力のストリームを開いてデータの受信を開始します。この行がないと、プログラムは標準入力からのデータを受け取ることができません。

<details><summary>process.stdinオブジェクトの詳細</summary>

:::note
##### process.stdinオブジェクト
process.stdinは、Node.jsの標準入力ストリームを表すオブジェクトです。標準入力ストリームは、ユーザーがターミナルやコマンドラインから入力するデータを受け取るために使用されます。process.stdinは、Readableストリームのインスタンスであり、データを読み取るための様々なメソッドやイベントを提供します。

:::
</details>

#### 2. エンコーディング設定
```js
process.stdin.setEncoding('utf8');
```
標準入力のエンコーディングをUTF-8に設定します。これにより、受け取るデータがUTF-8形式の文字列として解釈されます。

#### 3. 入力データ格納用配列作成
```js
var lines = [];
```
標準入力から読み込んだ行を格納するための空の配列を作成します。

#### 4. readlineモジュールを使用してインターフェースを作成
```js
var reader = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
});
```
readlineモジュールのcreateInterfaceメソッドを使用してインターフェースを作成します。inputに標準入力を指定し、outputに標準出力を指定しています。以降、このインターフェースを使って入力内容の読み取りや出力を行います。

<details><summary>readline.createInterfaceメソッドの詳細</summary>

:::note
##### readline.createInterfaceメソッド
###### 引数 (抜粋)
引数をオブジェクトで指定します。
- input (必須):
読み取りのための入力ストリームを指定します。通常、標準入力（process.stdin）を指定しますが、他のReadableストリームも指定できます。
- output (任意):
書き込みのための出力ストリームを指定します。通常、標準出力（process.stdout）を指定しますが、他のWritableストリームも指定できます。このストリームは、プロンプトメッセージやエコーバックされたユーザー入力のために使用されます。

###### 戻り値
readline.Interfaceオブジェクト
:::
</details>

#### 5. 入力内容を1行ずつ読み取り
```js
reader.on('line', (line) => {
  lines.push(line);
});
```
lineイベントは、標準入力から行が入力されるたびに発生します。イベント発生時の処理をコールバック関数で指定できますが、ここでは、入力された行をlines配列にpushメソッドで追加します。

<details><summary>readline.Interface.onメソッドの詳細</summary>

:::note
##### readline.Interface.onメソッド
###### 構文
```js
interface.on(eventName, listener)
    eventName (文字列): イベントの名前
    listener (関数): イベントが発生したときに呼び出されるコールバック関数
```

###### 主なイベント
1. line:
ユーザーが新しい行を入力したときに発生します。
コールバック引数: 入力された行（文字列）
2. close:
インターフェースが閉じられたときに発生します。通常、inputストリームが終了したときに発生します。
コールバック引数: なし
3. pause:
インターフェースが一時停止したときに発生します。
コールバック引数: なし
4. resume:
インターフェースが再開したときに発生します。
コールバック引数: なし
5. SIGINT:
ユーザーがCtrl+Cを押したときに発生します。
コールバック引数: なし
:::
</details>

#### 6. 出力
```js
reader.on('close', () => {
  console.log(lines[0]);
});
```
closeイベントは、標準入力のストリームが終了したときに発生します。イベント発生時の処理をコールバック関数で指定できますが、ここでは、lines配列の最初の要素、つまり最初に入力された行をコンソールに出力します。入力行が複数ある場合は、lines[i]のようにすることで(i + 1)行目を出力することができます。

### /dev/stdinを利用する方法
入力内容を一度に処理する方法です。

```js:/dev/stdinを利用
var lines = require("fs").readFileSync("/dev/stdin", "utf8").split("\n");
console.log(lines[0]);
```
このコードは以下のようなことを行なっています。
1行目で多くのことを行なっているので、分解して説明します。

#### 1. fsモジュール読み込み `require("fs")`
Node.jsのファイルシステムモジュールを読み込みます。このモジュールを使うことで、ファイルの読み書き操作を行うことができます。

#### 2. 入力内容全体を読み取り `readFileSync("/dev/stdin", "utf8")`
fsモジュールのreadFileSyncメソッドを使って、標準入力からデータを同期的に読み取ります。/dev/stdinはUNIX系システムにおける標準入力の特殊ファイルで、これを指定することで標準入力からのデータを読み取ります。
utf8はエンコーディングを指定しています。この指定により、読み取ったデータはUTF-8形式の文字列として扱われます。

<details><summary>fs.readFileSyncメソッドの詳細</summary>

:::note
##### fs.readFileSyncメソッド
###### 構文
```js
fs.readFileSync(path[, options])
    path (必須):読み取るファイルのパスを指定
    options (任意):オプションオブジェクトまたは文字列で、encodingやflagを指定
```

###### オプションオブジェクトのプロパティ
- encoding (文字列):
ファイルのエンコーディングを指定します。例: 'utf8'。エンコーディングを指定しない場合、デフォルトではBufferオブジェクトが返されます。
- flag (文字列):
ファイルシステムフラグを指定します。デフォルトは 'r'（読み取り専用）。

###### 戻り値
- Buffer:
encodingオプションが指定されていない場合、ファイルの内容がBufferオブジェクトとして返されます。
- string:
encodingオプションが指定されている場合、ファイルの内容が文字列として返されます。
:::
</details>

#### 3. 1行ずつ分割 `split("\n")`
読み取ったデータを改行文字（\n）で分割し、各行を配列の要素として格納します。結果として、標準入力から読み取った各行がlinesという配列に格納されます。

#### 4. 出力 `console.log(lines[0]);`
lines配列の1行目をコンソールに出力します。入力行が複数ある場合は、lines[i]のようにすることで(i + 1)行目を出力することができます。

## おわりに
これまでのJavaScript学習では標準入力やファイルを操作することがなかったので、最初にこれらのコードを目にした時は非常にとっつき辛く感じました。
今回の調査で処理の内容はクリアになったので、今後競技プログラミングに挑戦する際はすっきりした気持ちで臨めそうです。