2023. 2. 7. 17:47ㆍ개발/알고리즘 문제풀이
아래의 내용은 제가 Javascript로 백준 사이트에서 문제를 풀 때 사용하는 방법을 공유하고자 작성한 글입니다.
개선점에 대한 피드백 언제든 환영입니다!🙆♂️
왜 작성하게 되었는가?🧑💻
백준 문제를 풀다 문득 내가 처음에 백준을 접하고 힘들었던 내용을 내가 이해한 방법대로 알려주고 싶어서 적게 되었다.
Javascript로 코딩 테스트(이하 코테)를 준비하기로 마음먹고 백준에 들어가면 당황할 수밖에 없다. 대부분의 코딩 테스트 환경, 또는 프로그래머스와 같은 사이트에서 문제를 풀어보았다면
function solution(my_string) {
var answer = '';
return answer;
}
이러한 방식으로 테스트 케이스 입력에 대한 기본 뼈대를 제공한다. 이러한 방식의 장점은 해당 함수 내부만 작성하면 실행이 되도록 하여 함수 이외의 코드(테스트케이스의 입력, 함수 구동 등)를 신경 쓰지 않아도 되게 구성되어 있다. 하지만 백준은 그렇지 않다. 특히 Javascript는 input()으로 간단하게 입력받는 다른 언어에 비해 입력받는 방식이 독특(?)해서 처음 코테 연습을 위해 백준에 온 이들에게는 당황할 수밖에 없을 것이다. 나 또한 그러했다
백준(BOJ) 입력받는 방법🧑💻
입력을 받을 수 있는 방법이 대표적으로 2가지 있지만 추천되는 방식은 fs모듈을 사용하는 것이다. 하지만 좋은 개발자가 되기 위해 간단하게라도 readline모듈도 알아보자.
1. fs 모듈 사용
fs모듈(File System module)은 파일 처리와 관련된 전반적인 작업을 하는 모듈을 말한다. 해당 방법이 readline으로 받아오는 속도보다 빠르고 백준에서도 공식적으로 권장되는 방법이기 때문에 fs모듈로 연습하는 것을 추천한다.
const input = require('fs').readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt").toString().trim().split(" ").map(Number);
처음 문제 풀 때는 뜻을 모르고 했지만 문제를 풀 때마다 손으로 코딩하면 이 뜻을 이해하고 쉽게 적용할 수 있게 된다. node.js로 코딩할 때는 .(마침표)마다 끊어서 해석하면 이해하기 편하기 때문에 하나하나 끊어서 이해해 보자.
require('fs').readFileSync(process.platform === "linux"?"/dev/stdin":"./input.txt")
앞부분에 해당하는 이 부분은 그냥 이런 거구나 하고 넘어간 뒤 나중에 이해해도 된다.
require('fs').readFileSync(...) 왜 사용하나요?
require('fs')는 node.js에 내장되어 있는 fs모듈을 불러온다는 뜻이다.
readFileSync( path[ , option] )는 path에 불러올 파일의 상대적 위치를 사용하고 [option]으로 인코딩하는 방식으로 어떤 파일을 읽어올지 정한다고 생각하면 된다.
본문에 적힌 process.platform는 이 파일이 어떤 운영체제에서 실행되느냐에 대한 것인데 삼항 연산자를 사용하여 process.platform==="linux"가 참(true)이면 /dev/stdin을 실행하고 거짓(false)이면 ./input.txt를 실행한다. 백준 사이트에서 제출하면 linux(리눅스) 운영체제 상에서 실행되기 때문이다. VSCode 상에서 문제를 푸는 파일과 같은 폴더 안에 input.txt 파일에 문제의 입력값을 복사해 넣고 저장하면 문제를 푸는 환경(운영체제)이 window 또는 macOS이기 때문에 ./input.txt가 실행되는 것이다. 참고로 ./은 같은 폴더 안의 경로를 선택할 경우 사용한다.
VSCode로 작성하여 실행하는 방법에 관해서는 따로 자세하게 포스팅할 예정이다.
.toString().trim().split(" ").map(Number);
여기서부터 문제에 맞게 생각하고 적용해야 하는 부분이다.
.toString() 왜 사용하나요?
.toString()은 .앞의 내용을 문자열로 받겠다는 뜻이다. 그럼 입력이 숫자면 안 써도 되지 않느냐라고 할 수 있는데 그럼 오류가 발생한다. readFileSync의 인코딩 방법을 정해주지 않아 Buffer 객체로 반환되기 때문에 toString()을 무조건 써주도록 하자.
.trim() 왜 사용하나요?
뒤에 오는 공백을 제거한다는 뜻인 .trim()은 원래는 쓰지 않아도 상관없다. 그러나 우리는 Javascript로 코테를 준비하는 죄(?)로 알 수 없는 오류가 나면 .trim()을 빼먹지 않았는지 다시 한번 확인해 보자. 의외로 많은 문제들이 Javascript로 풀 때 뒤의 공백으로 오류가 나는 경우가 있다. 나중에 문자열의 답안을 출력할 때 공백이 있다면 answer.trim()처럼 뒤에 붙여서 사용하기도 한다.
.split() 왜 사용하나요?
이제 입력을 하나하나 받아오기 위해 분해하는 경우에 .split()을 사용한다. 띄어쓰기에 해당하는 값들을 나누려면 split(" ")(공백)을 사용하고 각각의 행(row)으로 나눌 때는 split("\n")을 사용한다.
.map() 왜 사용하나요?
마지막으로 map()은 이해하기 복잡하여 별도로 찾아보는 것을 추천하지만 간단히 설명하자면 () 괄호 안에 오는 조건으로 각각의 값을 바꿔주는 역할을 한다. 따라서 입력의 모든 값들이 숫자로 받아오고 싶다면 map(Number)을 사용한다. 단, 입력값의 개수가 하나일 경우 split과 map을 사용할 경우 오류가 발생할 수 있다.
제출 시 런타임 에러가 발생할 경우 위의 입력받아오는 방법이 잘못되지는 않았는지 다시 한번 확인해 보길 바란다.
입력값이 하나일 경우(문자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim();
//input: hello
//output: hello
입력값이 하나일 경우(숫자)
방법1)
const input = +require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim();
방법2)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim();
let num = +input; 또는 parseInt(input) 또는 Number(input)
//input: 8
//output: 8
입력값이 띄어쓰기로 구분된 한 줄의 값들인 경우(문자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split(" ");
//input: hello world
//output: ['hello', 'world']
입력값이 띄어쓰기로 구분된 한 줄의 값들인 경우(숫자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split(" ")
.map(Number);
//input: 8 7 56
//output: [8, 7, 56]
입력값이 여러 줄의 값들인 경우(문자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split("\n");
//input:
//a
//b
//c
//d
//output: ['a', 'b', 'c', 'd']
입력값이 여러 줄의 값들인 경우(숫자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split("\n")
.map(Number);
//input:
//1
//2
//3
//4
//5
//output: [1, 2, 3, 4, 5]
입력값이 여러 줄의 값들이 띄어쓰기로 구분되어 있는 경우(문자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split("\n")
.map((el) => el.split(" "));
//input:
//ab cd
//ef gh
//my name is minjoon
//hi hello
//output: [
// [ 'ab', 'cd' ],
// [ 'ef', 'gh' ],
// [ 'my', 'name', 'is', 'minjoon' ],
// [ 'hi', 'hello' ]
//]
입력값이 여러 줄의 값들이 띄어쓰기로 구분되어 있는 경우(모두 숫자)
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt")
.toString()
.trim()
.split("\n")
.map((el) => el.split(" ").map(Number));
//input:
//3
//1 2
//3 4 5 6
//5 3 2 5
//0 1 1 0
//output: [ [ 3 ], [ 1, 2 ], [ 3, 4, 5, 6 ], [ 5, 3, 2, 5 ], [ 0, 1, 1, 0 ] ]
위의 방법들로 대부분 해결 가능하다.
가장 아래 두 가지 방법은 input의 값이 이중배열로 나오기 때문에 이중배열에 대한 공부가 선행되어야 한다.
예시로 10870번 개수 세기를 예시로 들어 설명하겠다.
예제 입력 1
11
1 4 1 2 4 2 4 2 3 4 4
2
예제 출력 1
3
예제 입력 2
11
1 4 1 2 4 2 4 2 3 4 4
5
예제 출력 2
0
답안
const input =
require('fs').
readFileSync(process.platform === "linux" ? "/dev/stdin" : "./input.txt").
toString().
split("\n").
map(e => e.split(" ").map(Number));
//예제 1의 입력을 받으면
//[ [ 11 ],[ 1, 4, 1, 2, 4, 2, 4, 2, 3, 4, 4 ],[ 2 ] ]
const N = input[0][0];//이중배열 활용 input의 첫 번째 요소의 첫 번째 요소 => 11
const v = input[2][0];//이중배열 활용 input의 세 번째 요소의 첫 번째 요소 => 2
let count = 0;
for (let i = 0; i < N; i++) {
if (v === input[1][i]) {
count++;
}
}
console.log(count);
2. readline 모듈 사용
readline모듈은 Javascript 내장 모듈로 입력되는 값들을 readline을 통해 한 줄씩 읽어오고 입력과 출력을 아래와 같이 따로 분리하여 작성하는 형태로 구성되어 있다.
//readline 모듈 불러오기
const readline = require('readline');
//인터페이스 객체 생성하기
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
//입출력 처리하는 코드
rl.on('line', function (line) {
//입력 처리하는 코드
console.log(line);//입력 출력
rl.close();
}).on('close', function () {
//입력을 받은 뒤 실행할 코드
process.exit();//종료문
});
readline모듈에 대해서는 코드의 구성과 역할을 이해하는 정도만 해두자.
본 글에서는 정확하게 알지 못하는 방법을 알려주는 것이 글의 신뢰도가 낮아지게 한다고 판단되어 위의 내용 정도만 간략하게 확인하고 다른 이들의 설명을 보는 것을 추천한다.
출력해서 문제 푸는 방법
입력을 받았다면 출력을 해야 문제를 맞게 풀고 있는지 확인할 수 있을 텐데 vscode를 활용하여 어떻게 출력을 받을 수 있는지 알려주겠다. 먼저 vscode를 실행한다.
위의 그림에 적힌 순서대로 클릭한다.
1. extenstions 클릭 -> 2. 검색창에 "code runner" 입력 -> 3. 첫 번째로 나오는 Code Runner의 Install 버튼 클릭
위와 같이 설정하고
위와 같이 문제를 풀 js파일과 입력값을 넣을 input.txt이 같은 폴더에 있는지 확인한다.
js 파일에 문제를 풀고 Ctrl + Alt + n 을 누르면 아래의 OUTPUT 창에 출력값이 출력된다. 출력이 안되거나 이상한 값이 출력되면 js파일과 input.txt을 저장했는지 먼저 확인하고 js 코드를 클릭하고(js 파일을 선택하고) ctrl +alt+n 을 했는지 잘 확인한다.(가끔 input.txt 파일을 수정하다가 ctrl+alt+n을 실행하면 Code language not supported or defined.이라는 경고창이 뜨기 때문에 js 파일을 선택하고 해당 단축키를 눌렀는지 확인한다.)
Ctrl + Alt + n //실행 방법
1000번 문제를 예시로 들어서 보여주면 다음과 같다.
input.txt 파일 안에는 백준 사이트의 예제 입력을 복사해서 붙여 넣은 다음 저장하면 되고, js파일에는 앞에서 알려준 입력받는 부분을 가장 처음 적어주고 알맞게 입력받았는지 확인하고 싶으면 console.log(input)을 적고 저장한 뒤 Ctrl+Alt+n을 통해 실행하여 아래 OUTPUT창에 입력값과 동일하게 출력되면 알맞게 설정된 것이다.
문제를 푸는 중간마다 console.log를 통해 설정한 변수나 결괏값을 출력해 보는 습관을 가지면 실수 없이 문제를 풀기 좋으니 참고하자.
앞서 말했듯이 우리는 Javascript로 코테를 준비하는 죄(?)가 있기 때문에 어쩔 수 없는 억까를 당하기도 한다.
해당 문제는 정답률이 60%를 넘지만 node.js로 푼 사람들의 정답률은 20% 밖에 되지 않는 기이한 문제이다. 입력을 const input = require('fs').readFileSync(0).toString().trim().split("\n");로 받아오거나 readline 모듈로 사용해야 해결된다.
해당 문제는 node.js로 푼 사람이 0명이며 말 그대로 억까를 당했다. 메모리 초과문제를 해결할 수 없다.
10886번 0 = not cute / 1 = cute
해당 문제는 270번의 제출이 있었지만 2명만 맞춘 문제이다. 그마저도 4,6년 전에 해결되어 최근 버전 node.js로는 메모리 초과가 발생하는 것 같다.
이러한 억까에 대비하기 위해 꼭 해당 문제의 맞힌 사람에 들어가서 node.js로 맞힌 사람이 존재하는지 먼저 파악해 보는 것을 강력히 추천한다.
특히 아래와 같이 언어 제한이 붙어있는 경우 꼭 맞힌 사람을 확인해 보자.
백준에서도 node.js로 에러가 난다는 것을 인지하고 있지만 1년째 해결되지 않은 것 같다. 만약 여러 방법을 통해서 시도했는데도 풀리지 않는다면 어쩌면 당신의 잘못이 아닐 수 도 있다. 추천하는 바는 python 또는 java 등을 같이 공부하여 안 되는 문제에 적용하는 방식도 많은 도움이 된다.
마무리🧑💻
처음부터 문제를 풀기 쉽지 않을 것이다. 어쩌면 내가 이것도 못 푸는 걸까?라는 생각이 들 수도 있다. 그러나 최대한 풀어보고도 안된다면 구글에 문제 번호 + nodejs(10870 nodejs)로 검색하여 문제에 익숙해지는 것을 목표로 하자. (꿀팁: node.js 풀이가 대체로 안 나오는 편이기 때문에 python이나 java로 검색하여 접근법에서 힌트를 얻을 수 있다.)
Github 구경하기
'개발 > 알고리즘 문제풀이' 카테고리의 다른 글
[백준BOJ] 24263번 알고리즘 수업 - 알고리즘의 수행 시간 2 - JavaScript(node.js) (0) | 2023.03.05 |
---|---|
[백준BOJ] 24262번 알고리즘 수업 - 알고리즘의 수행 시간 1 - JavaScript(node.js) (0) | 2023.03.05 |
[백준BOJ] 2738번 행렬 덧셈 - JavaScript(node.js) (0) | 2023.02.12 |
[백준BOJ] 10823번 더하기 2 - JavaScript(node.js) (0) | 2023.02.10 |
[백준BOJ] 2798번 블랙잭 - JavaScript(node.js) (0) | 2023.02.09 |