[JS] #1 입력받기

fs.readFileSync() 메소드 알아보기😎


Table Of Contents


TL;DR


  • 입력 통째로 받기
    const input = require("fs").readFileSync(process.stdin.fd).toString();
  • 메소드로 입력 다듬기
    input.split("\n").map(Number);

들어가기


FE 포지션으로 지원하면 JS로 코테를 보도록 하는 곳이 점점 늘어나는 것 같다

학교에서 자료구조/알고리즘을 C/C++로 배우다가 갑자기 JS 코테를 마주하면 입력부터 막히는 일이 일어난다🥰

프로그래머스처럼 입출력을 알아서 다 해주는 플랫폼에서는 크게 신경 쓰지 않아도 되지만... 나는 입력부터 처리해야 하는 코테를 만나버렸다!

 

JS로 코테를 본다고는 하지만 정확히 말하자면 Node.js라는 JS 런타임 환경을 이용한다.

따라서 입력도 Node.jsfs 모듈이나 readline 모듈을 이용해서 처리한다. 나는 readline은 잘 사용하지 않아서... 오늘은 fs.readFileSync()를 통해서 입력 받는 방법을 알아보자.


fs.readFileSync()


fs 모듈을 이용해서 파일을 읽는 방법이다. Node.js docs - fs.readFileSync(path[, options])에서 명세를 확인할 수 있다. fs.readFile()라는 비동기 버전 메소드도 있기 때문에 이쪽도 같이 참고하면 좋다.

fs.readFileSync(path[, options])

  • path에는 읽으려고 하는 파일의 경로 또는 file descriptor를 넘겨준다.
    • option으로는 encoding, flag를 넘겨 줄 수 있다. 보통 코테에서 flag는 잘 건드리지 않는 듯 하다.
  • 반환값으로는 string 혹은 Buffer 타입의 값을 넘겨준다. 기본적으로 Buffer 타입으로 반환하고, 위에서 encoding 옵션을 넘겨준 경우에 string 타입으로 반환받을 수 있다.

사용법

1. fs 모듈 가져오기

readFileSyncfs 모듈 안에서 구현되므로 우선 fs 모듈을 불러온다.

const fs = require("fs");

보통 코테 사이트들은 ESM이 아니라 CommonJS를 쓰니까 import 말고 require("fs")로 불러온다.

2. readFileSync로 입력 받기

그리고 readFileSyncprocess.stdin.fd를 인자로 넘겨서 입력을 통째로 받아올 수 있다.

주의하자, 공백으로 구분된 단어나 한 줄 단위가 아니라 입력을 끝까지 받아온다.

const rawInput = fs.readFileSync(process.stdin.fd);

process.stdin.fd란?

위에서 readFileSync의 인자로는 파일 경로나 fd(file descriptor)를 넣어야 한다고 했다.

process.stdin.fdNode.js에서 표준 입력의 fd를 가리킨다.

  • cf) processNode.js의 프로세스에 대한 정보를 가지는 글로벌 객체이다. 따라서 별도의 import 없이 사용할 수 있다!
  • Node.js docs - Process

일반적으로 UNIX 계열 운영체제에서는 표준 입력(stdin)의 fd 번호가 0이기 때문에 process.stdin.fd 대신에 바로 0을 넣을 수는 있다. 하지만 0이 표준 입력의 fd를 가리킨다고 명확히 알기 어렵기 때문에 process.stdin.fd를 써주는 게 좋다.

 

대안이 있나요?: "/dev/stdin"

다른 블로그들을 보니 readFileSync("/dev/stdin")처럼 사용하고 있는 사람들이 많았다.

UNIX(와 UNIX 계열 운영 체제들)에는 /dev/stdin이나 /dev/stdout, /dev/stderr같은 Character special files(문자 특수 장치) 가 존재한다. 이들은 마치 일반 파일처럼 접근할 수 있으면서, 표준 입출력 시스템 호출을 통해서 장치와 상호작용할 수 있도록 해준다. /dev/stdin은 이들 중에서도 표준 입력을 가리킨다.

참고로, Windows는 UNIX 계열 os가 아니다! 따라서 "/dev/stdin"라는 문자 특수 장치도 사용할 수 없다.

백준 도움말에서도 Node.js의 예시로 해당 파일을 사용하는 모습을 찾아볼 수 있다.

  • 백준이나 다른 cp 사이트에서는 채점 서버가 보통 UNIX 계열에서 돌아가기 때문에 이렇게 사용할 수도 있다.
  • 14681번 문제"/dev/stdin"을 사용할 경우, 런타임 에러 (Error: EACCES: permission denied)를 받게 된다. 접근 권한 설정 관련 문제인 것 같은데 process.stdin.fd나 0을 쓰면 해결 가능하다.

3. toString()으로 문자열로 변환하기

위에서 언급했는데, 별도로 encoding 인자를 넘기지 않으면 리턴값은 Buffer 타입이 된다. 따라서 꼭 toString() 메소드를 호출해서 입력을 string 타입으로 바꿔주자.

const input = rawInput.toString();

이 방법 대신에 encoding을 지정할 수도 있다. 우리가 사용하는 파일들은 대부분 UTF-8 인코딩을 사용하므로,

const input = fs.readFileSync(process.stdin.fd, { encoding: "utf8" }); // 또는 const input = fs.readFileSync(process.stdin.fd, "utf8");

이렇게 호출하면 처음 코드와 같은 기능을 한다.

아마 코테에서도 정말 특수한 경우가 아니면 UTF-8 인코딩을 사용할 테니 어느 쪽을 사용해도 상관 없을 것 같다.

후처리

이제 문제에서 주어지는 입력 자체는 모두 받아왔다!

하지만 아직 할 일이 남았다. 우리가 받은 입력은 하나의 문자열이기 때문이다.

이 문자열을 자르고 숫자로 파싱하는 방법을 알아보자.

 

예시가 필요할 것 같아서 BOJ에서 A+B - 3 문제를 가져왔다.

예제 입력은 아래와 같다.

5 1 1 2 3 3 4 9 8 5 2

1. 줄 나누기

우리가 입력받은 문자열은 지금 아래와 같은 상태다.

const input = fs.readFileSync(process.stdin.fd, "utf8"); //input = "5\n1 1\n2 3\n3 4\n9 8\n5 2";

줄바꿈 문자를 기준으로 문자열을 나눠 주자.

split() 메소드는 인자로 패턴을 넘겨주면 해당 패턴에 맞는 부분을 삭제하고 남은 문자열을 배열로 반환해준다. 지금은 줄바꿈 문자를 모두 삭제해야 하므로 .split('\n')처럼 호출했다.

const lines = input.split('\n'); // lines = ["5", "1 1", "2 3", "3 4", "9 8", "5 2"];

2. 변수에 할당하기

위에서 받은 값들은 전부 하나의 배열에 담겨 있다. 어디에 원하는 값이 있는지 알기 불편하니 각 값들을 적절한 변수에 할당해보자.

먼저, 첫 번째 문자열은 TTT값이다. lines[0]으로 접근해서 받아올 수 있는데, 아직 문자열이므로 정수로 바꿔주도록 하자.

const T = parseInt(lines[0]); // T = 5;

 

나머지 값들은 a, b값들을 담고 있는 문자열들이다. arr이라는 변수를 만들어서 나머지 문자열을 다 담아준다.

slice()라는 메소드를 사용해 1번 인덱스부터 T+1 인덱스 직전까지의 문자열들을 받아온다.

const arr = lines.slice(1, T + 1); // arr = ["1 1", "2 3", "3 4", "9 8", "5 2"];

 

slice(begin[, end])에서 end값을 지정하지 않으면 배열 끝까지 잘라 오기 때문에 여기에서는

const arr = lines.slice(1);

처럼 사용해도 똑같은 결과를 얻을 수 있다.

 

만약 n줄, m줄이 각각 주어지는 경우에는 아래처럼 받아오면 된다.

// 첫 번째 줄에 n, m이 주어졌다고 가정한다. const nArr = lines.slice(1, n + 1); const mArr = lines.slice(n + 1, n + m + 1);

3. 정수로 변환하기

지금 arr 배열에는 "a b" 형태의 문자열이 들어 있다.

이 배열에 있는 모든 문자열들을

  1. 공백을 기준으로 자르고

  2. 결과물인 ["a", "b"]에서 "a", "b"를 정수로 변환해야 한다.

  3. 모든 arr 배열의 문자열을 공백을 기준으로 자르기

    • map() 메소드를 이용하여 모든 문자열을 순회하며 split() 메소드를 호출해주자.
    const splitArr = arr.map(line => line.split(" "));
  4. 각 문자를 정수로 변환하기

    • parseInt() 메소드를 호출하면 인자로 넘겨준 문자열을 정수로 반환받을 수 있다.
      • 10진법 이외에 다른 진법으로 된 문자열도 변환 가능하다.
      • 단, 소수는 변환하지 못한다.
      const parsedArr = splitArr.map(str => parseInt(str, 10));
    • Number() 메소드를 이용할 수도 있다.
      • 소수도 변환할 수 있다.
      const parsedArr = splitArr.map(str => Number(str));
      • 변환하고자 하는 문자열(str) 이외에 다른 인자가 필요하지 않으므로 아래처럼 줄여 쓸 수도 있다.
      const parsedArr = splitArr.map(Number);
    • + 연산자를 사용할 수도 있다.
      const parsedArr = splitArr.map(str => +str);

 

이 과정을 하나로 합쳐서

const parsedArr = arr.map(line => line.split(" ").map(Number));

처럼 나타낼 수 있다.

4. 문제 풀기

지금까지 쓴 코드를 다시 보자.

const input = require("fs").readFileSync(process.stdin.fd, "utf8"); const lines = input.split('\n'); const T = parseInt(lines[0]); const arr = lines.slice(1, T + 1); const parsedArr = arr.map(line => line.split(" ").map(Number));

이제 T와 parsedArr를 참조해서 문제를 해결할 수 있다!

for (const pair of parsedArr) { const [a, b] = pair; console.log(a + b); }

참고


https://nodejs.org/api/fs.html#fsreadfilesyncpath-options https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number