백준에서 Node.js로 입력을 받는 방법은 크게 두 가지가 있다. 첫 번째는 readline 모듈을 사용하는 것이고, 두 번째는 fs 모듈을 사용하는 것이다. (이 글에서는 fs 모듈에 대해서만 다루겠다.)
Python으로 백준 문제를 풀 때 시간 초과가 나는 것을 막기 위해 input() 대신 sys.stdin.readline()을 쓰는 것처럼, Node.js도 readline보다 fs모듈을 사용하는 것이 더 빠르다.
var fs = require('fs');
var input = fs.readFileSync('/dev/stdin').toString().split(' ');
var a = parseInt(input[0]);
var b = parseInt(input[1]);
console.log(a+b);
위 코드는 백준 언어 도움말에서 제공하는 코드다.
FileSystem의 약자인 fs 모듈은 파일 처리를 하는 모듈로, 직접 입력 파일을 읽어와서 처리한다. 위 코드는 입력값 전체를 하나의 문자열로 만들어 split 메서드로 배열로 만들어 그 안의 요소를 가져다 쓰는 방식으로 입력을 받는다. 하지만 백준에서 제공하는 위 코드를 사용했는데도 에러가 발생하는 경우가 잦다.
지금까지 계속 런타임에러에 시달리면서 터득한 내 나름대로의 값을 입력받는 코드들을 정리해보았다.
// 1. 하나의 값을 입력받을 때
const input = require('fs').readFileSync('/dev/stdin').toString().trim();
// 2. 공백으로 구분된 한 줄의 값들을 입력받을 때
const input = require('fs').readFileSync('/dev/stdin').toString().trim().split(' ');
// 3. 여러 줄의 값들을 입력받을 때
const input = require('fs').readFileSync('/dev/stdin').toString().trim().split('\n');
// 4. 첫 번째 줄에 자연수 n을 입력받고, 그 다음줄에 공백으로 구분된 n개의 값들을 입력받을 때
const [n, ...arr] = require('fs').readFileSync('/dev/stdin').toString().trim().split(/\s/);
// 5. 첫 번째 줄에 자연수 n을 입력받고, 그 다음줄부터 n개의 줄에 걸쳐 한 줄에 하나의 값을 입력받을 때
const [n, ...arr] = require('fs').readFileSync('/dev/stdin').toString().trim().split('\n');
// 6. 하나의 값 또는 공백으로 구분된 여러 값들을 여러 줄에 걸쳐 뒤죽박죽 섞여서 입력받을 때
// ex) n 입력 - 공백으로 구분된 n개의 값 입력 - m 입력 - 여러 줄에 걸쳐 m개의 값 입력
const input = require('fs').readFileSync('/dev/stdin').toString().trim().split(/\s/);
const n = input[0];
const n_arr = input.slice(1, n+1);
const [m, ...m_arr] = input.slice(n+1);
// 2~6에서 입력받는 값들을 모두 String에서 Number로 바꾸려면 split()뒤에 .map(Number)를 추가
위 코드를 보고 몇 가지 의문이 들 수 있다.
문자열 값을 입력받는 것이라면 toString()을 안 붙여도 되지 않나요?
require("fs").readFileSync("/dev/stdin")의 반환값은 문자열이 아닌 Buffer 객체다. readFileSync의 인수로 인코딩을 지정해주지 않으면 Buffer 객체를 반환한다. 따라서 문자열로 바꾸어주지 않으면 예기치 못한 오류가 날 수 있다. 문자열로 바꾸기 위해서는 위의 코드처럼 toString() 메서드 또는 문자열 연결 연산을 통해 Buffer 객체를 문자열로 바꾸거나, readFileSync의 두 번째 인수로 인코딩을 지정해주면 된다.
const input = require('fs').readFileSync('/dev/stdin');
console.log(typeof input); // object
const input2 = require('fs').readFileSync('/dev/stdin').toString();
console.log(typeof input2); // string
const input3 = require('fs').readFileSync('/dev/stdin')+'';
console.log(typeof input3); // string
const input4 = require('fs').readFileSync('/dev/stdin', 'utf8');
console.log(typeof input4); // string
trim()은 왜 쓰는 건가요?
일부 입력값의 마지막에 개행문자가 포함된 경우가 종종 있다. 이런 경우 split("\n")할 경우 공백문자 하나를 더 갖는 배열을 반환한다. 이를 방지하기 위해서 trim()을 사용한다.
const text = '가\n나\n다\n';
console.log(text.split('\n')); // ['가', '나', '다', '']
console.log(text.trim().split('\n')); // ['가', '나', '다']
4번이나 6번을 쓰면 속도가 너무 느린 것 같아요.
split('/\s/')는 속도가 느리다. 처음 자바스크립트로 백준 문제를 푸시는 분들의 편의를 위해 위와 같이 작성하였다. 입력값이 엄청 크거나, 수행 시간을 단축하고 싶다면 split('/\s/')를 사용하지 말고 split('\n')로 한 줄 씩 나눈 뒤, 한 줄을 다시 한 번 더 나누는 방식으로 입력을 받아야 한다.
각 경우에 해당하는 백준 문제를 하나씩 뽑아 보았다.
1. 하나의 값을 입력받을 때
2. 공백으로 구분된 한 줄의 값들을 입력받을 때
3. 여러 줄의 값들을 입력받을 때
4. 첫 번째 줄에 자연수 n을 입력받고, 그 다음줄에 공백으로 구분된 n개의 값들을 입력받을 때
이 문제는 n 말고 x도 있어서 const [n, x, ...arr]로 풀면 된다.
5. 첫 번째 줄에 자연수 n을 입력받고, 그 다음줄부터 n개의 줄에 걸쳐 한 줄에 하나의 값을 입력받을 때
6. 하나의 값 또는 공백으로 구분된 여러 값들을 여러 줄에 걸쳐 뒤죽박죽 섞여서 입력받을 때
위의 코드와는 상황이 조금 다르지만 split(/\s+/)로 분리하여 적절하게 변수를 지정해주면 된다.
이렇게 입력에 신경을 썼음에도 런타임에러가 나거나 기타 다른 이유로도 문제가 통과되지 않는 경우가 있다.
1. 특정 문제에서 readFileSync가 먹히지 않는 경우
위 문제는 readline 모듈을 사용하면 풀리지만 어떤 이유에서인지 fs모듈의 readFileSync를 사용하면 런타임에러가 발생한다.
최근에 백준에 이와 관련된 공지가 올라오기도 하였다.
www.acmicpc.net/board/view/66736
2. Node.js로는 해당 문제의 메모리제한을 넘기지 않고 풀 수 없는 경우
https://www.acmicpc.net/problem/11723
https://www.acmicpc.net/problem/2293
https://www.acmicpc.net/problem/10886
[10989번 수 정렬하기 3], [11723번 집합], [2293번 동전 1]
위 세 문제는 현재 Node.js로 통과한 사람이 단 한 명도 없다... (2021.09.10 확인)
[10886번 0 = not cute / 1 = cute]
이 문제는 현재(2022.10.14 확인) 2명이 통과한 기록이 있으나, 이는 각각 4년 전, 2년 전 기록으로 Node.js의 버전이 높아짐에 따라 현재는 통과가 되지 않는다. (3 ~ 4년전 Node.js로 푼 기록이 압도적으로 수행 시간이 짧은 이유)
현재 몇몇 문제에 대한 Node.js 메모리 제한 변경 논의가 이루어지고 있다.
댓글에 위 문제들 중 일부가 언급되어 있는데, 조만간 메모리 제한이 완화될 가능성이 보인다.
(2021.07.30 확인)
https://www.acmicpc.net/board/view/71957
하지만 실제로 불가능한 것인지는 알 수 없다.
이 문제 역시 Node.js로 풀 수 없다는 말이 게시판에 올라왔었다. (www.acmicpc.net/board/view/32852)
그러나 1년 후부터 이 문제를 Node.js로 통과한 사람들이 등장하기 시작했다.
여하튼 Node.js로 백준에서 문제 풀기가 참 까다로운 것 같습니다.
혹시 틀린 내용이 있다면 지적 환영합니다!
'IT > JavaScript' 카테고리의 다른 글
[JS] 자바스크립트로 엑셀 파일(xlsx) 만들어서 다운받기 (0) | 2022.08.05 |
---|---|
[JS] Invalid regular expression: invalid group specifier name (0) | 2022.06.21 |
[JS] Selection API로 선택된 텍스트 정보 가져오기 (0) | 2022.06.01 |
[JS] 자바스크립트의 배열 생성 방법 (0) | 2021.07.29 |
[JS] 함수의 매개변수와 RangeError (0) | 2021.07.23 |