JavaScript 로 코테 풀 때 표준 입력을 어떻게 받을까

Je-yeong·2023년 6월 3일
0

JavaScript 의 경우 표준 입출력을 사용하는 알고리즘 문제(코딩 테스트)를 풀기가 좀 곤란하다.

Web Browser 환경에서 내장 함수인 prompt() 를 사용하여 사용자로부터 입력 값을 받아올 수 있듯이 Node.js 환경에서도 단순히 어떤 한 함수를 호출하여 표준 입력 값을 받아 올 수 있다면 정말 좋을 것이다.

process in Node.js

Node.js 환경에서 표준 입출력은 process 라는 내장 모듈을 통해 접근 가능하다.

const { stdin } = require('process');

stdin.on('data', chunk => console.log(chunk.toString()));

이렇게 stdin 이라는 Readable Stream 객체를 사용할 수 있다.

process.stdin 스트림을 직접 쓰면 불편한 점

toString() 의 귀찮음

하지만 저런 식으로 stdin 스트림의 data 이벤트를 리스닝하는 방식을 사용하면 한가지 불편한 점이 있는데 바로 넘어오는 이벤트 인자(chunk)가 string 이 아닌 Buffer 라는 점이다. 그래서 문자열로 사용하기 위해 toString() 을 부른 것이다.

스트림의 data 이벤트의 인자로 string 이 넘어오는 경우도 있다. e.g. Readable.from('FOO\nBAR') 와 같이 문자열로 만든 스트림의 경우가 그렇다.

readline in Node.js

Node.js 의 readline 은 하나의 Readable Stream 과 하나의 Writable Stream 에 대해서 문자열을 한 줄씩 읽고 쓰는 데 도움을 주는 모듈이다. 이때 readline.Interface 또는 readlinePromises.Interface 가 사용되는데

const { stdin, stdout } = require('process');
const { createInterface } = require('readline');

const rl = createInterface({input: stdin, output: stdout});

이런 식으로 createInterface() 를 통해 readline.Interface 인스턴스를 생성할 수 있다. 이때 인자로 옵션을 주는데 위와 같이 input 에는 Readable Stream, output 에는 Writable Stream 을 하나씩 주면 된다. 보통 출력은 간편히 console.log() 를 사용하기 때문에 output 을 생략하기도 한다.

readline 을 쓰면 좋은 점

일관된 동작

디버깅을 위해 하드코딩된 하나의 입력에 대한 프로그램의 동작을 보고 싶은 경우가 있다.

한편 위에서 말했듯 process.stdin 스트림을 직접 쓰면 data 이벤트의 인자가 Buffer 인데 문자열로 하드코딩된 입력스트림(e.g. Readable.from('FOO\nBAR))의 경우는 string 이다. 이 일관되지 않은 동작은 문제 풀이에 방해가 될 수 있다.

반면 readlineline 이벤트는 인자가 항상 문자열이다. 또한 줄바꿈 문자(\n, \r\n)가 포함되어 있지 않다. 따라서 일관된 동작을 기대할 수 있다.

Base Code

readlineprocess 모듈을 활용하여 표준 입출력을 사용하는 문제를 위한 JavaScript Base Code 를 작성해보자.

const main = ({ next, nextInt }) => {
    const foo = next();
    const bar = nextInt();
    // ...
};

(func => {
    const { createInterface } = require('readline');
    const { stdin } = require('process');
    // const { Readable } = require('stream');

    const input = stdin;
    // const input = Readable.from('3 2\nB A R\nA\nB');

    const tokens = [];
    let cursor = -1;
    const scanner = {
        next: () => tokens[++cursor],
        nextInt: () => parseInt(scanner.next()),
    };

    createInterface({ input })
        .on('close', () => func(scanner))
        .on('line', line => line
            .trim()
            .split(/\s+/)
            .forEach(token => tokens.push(token)));
})(main);

main 함수에 실제 문제 풀이 구현이 들어간다.

이 코드에는 공백과 줄바꿈을 구분하지 않고 한 토큰씩 사용하려는 의도가 담겨 있다. (너무 길긴 하다.)

API

next()

입력에서 한 토큰을 꺼낸다. (문자열)

nextInt()

입력에서 한 토큰을 꺼낸다. (정수)

한계점

너무 긴 것이 문제이다. 문제마다 매번 이런 반복적인 코딩은 하고 싶지 않다. JavaScript 는 표준 입출력을 사용하는 문제에 적합한 언어가 아니라고 생각한다. (Web Browser 환경에서 태어난 언어이기 때문일까.)


미니멀 (난독화) 폼

const main = ({ next, nextInt }) => {
    const foo = next();
    const bar = nextInt();
    // ...
};

(_=>{const a=[];let b=-1;const $={next:()=>a[++b],nextInt:()=>parseInt($.next())};require('readline').createInterface({input:require('process').stdin}).on('close',()=>_($)).on('line',_=>_.trim().split(/\s+/).forEach(_=>a.push(_)));})(main);
profile
Versatile

0개의 댓글