구현기/알고리즘 문제풀이

백준 8958 : OX퀴즈 nodejs 풀이

Sadie Kim 2023. 6. 11. 16:30

문제 링크는 이곳 클릭!

우선 정답 코드는 다음과 같다.

let fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

let number = Number(input[0]);

for (let i = 1; i <= number; i++) {
  let curScore = 0;
  let score = 0;

  for (let j = 0; j < input[i].length; j++) {
    if (input[i][j] === "O") {
      curScore++;
    } else if (input[i][j] === "X") {
      curScore = 0;
    }
    score += curScore;
  }
  console.log(score);
}

아래는 나의 채점현황이다.

아주 끔찍한 시간을 보냈다.
가장 끔찍한 점은 모든 답들이 로컬 환경에서 테스트했을 때는 모든 예제를 무사통과했다는 점이다.
로컬에서 테스트했을 땐 멀쩡히 돌아가던 게 백준에서 돌리면 안 됐던 적이 한두번이 아니긴 하지만 이렇게 오래 해답을 못 찾은 적은 처음이었다....
그래서 오답이었던 답과 함께 왜 오답이 됐는지를 분석해보고자 이 글을 포스팅하게 되었다.

패인1. /r을 지우는 처리를 하면 안 됨

보통 로컬환경에서의 테스트는 메모장에 입력값을 입력하고, fs.readFileSync를 통해 해당 값을 읽는 방식으로 이루어지게 된다.
그런데 메모장을 통해 값을 입력받으면 콘솔로 찍어봤을 때 라인 끝마다 /r이 붙어있는 모습을 확인할 수 있다.

const fs = require("fs");
let input = fs.readFileSync("test.txt").toString().split("\n");

input.shift();

console.log(input);

이 코드로 입력값을 읽어오면,

이렇게 끝마다 \r이 붙어서 나온다.
때문에 이 점이 거슬려서 입력값을 이렇게 처리해서 받아왔다.

let input = fs.readFileSync("/dev/stdin").toString().split("\r\n");

하지만 \r이 붙는 건 메모장에서 입력받을 때만의 특성인지 백준에선 이렇게 처리하는 순간 무조건 오답 처리된다.
실제로 막판에 저 부분만 \n으로 바꿔주는 순간 정답이 되었다.
하긴 그동안 \r이 붙는 걸 많이 본 것도 같은데 처리를 안 하고도 정답이 되었던 걸 보고 눈치챘어야 하는데... 너무 신경쓰여서 괜히 먼 길을 가버리고야 말았다.

패인 2. 첫 줄에 받는 테스트케이스의 개수 데이터를 예외처리로 얼렁뚱땅 넘기지 말자.

초반에는 다음과 같은 방식으로 답안을 적었다.

const fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

input.shift();

input.map((ques) => {
  const quesArray = ques.split("");
  let curScore = 0;
  const answer = quesArray.reduce((acc, cur, idx) => {
    if (cur === "O") {
      curScore++;
      return acc + curScore;
    } else {
      curScore = 0;
      return acc;
    }
  }, 0);
  console.log(answer);
});

보다시피 js의 array 메소드를 많이 사용했다. shift도 이용하고 map도 쓰고 reduce도 이용했다.

자바스크립트에는 입력값을 array로 받고, 또 array.length가 있기에 입력값의 첫번째 줄에 나오는 테스트케이스의 개수는 사실 쓸데없는 데이터라고 느꼈다. 그래서 shift로 지워 버렸다.
map은 간편함+습관적으로 사용한 것 같고... reduce는 쓸만한 상황이었다고 생각한다.

그런데 계속 오답처리돼서 검색해 보니 다들 간단한 for문 정도만 사용해서 문제를 풀었더라...
그래서 저 요소들을 다 뺐더니 정답이 됐다.

그렇다면 어떤 것 때문에 오답이었을까.
그걸 테스트해 봤다.

array.shift()로 첫 줄 테스트케이스의 개수를 지워 버리면 오답이 된다.

다음 코드를 제출했을 때, 오답이 되는 걸 확인했다.

const fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

input.shift();

for (let i = 0; i < input.length; i++) {
  let curScore = 0;
  let score = 0;

  for (let j = 0; j < input[i].length; j++) {
    if (input[i][j] === "O") {
      curScore++;
    } else {
      curScore = 0;
    }
    score += curScore;
  }
  console.log(score);
}

정답 코드에서 초반 부분만 shift()로 변형하고 나머진 그에 맞게 조작한 답이다.
로컬에선 모든 예제에 알맞은 출력값을 냈지만, 제출하면 오답 처리된다.

map() 함수로 input을 통째로 받되 입력값의 첫 줄 테스트케이스의 개수를 예외처리할 경우, 오답 처리된다.

만약 shift()를 안 쓰고 map()으로 첫 줄을 예외처리할 경우, 다음과 같이 쓸 수 있다.

let fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

input.map((ques, idx) => {
  if (idx === 0) return;

  const quesArray = ques.split("");
  let curScore = 0;
  let score = 0;

  for (let j = 0; j < quesArray.length; j++) {
    if (quesArray[j] === "O") {
      curScore++;
    } else {
      curScore = 0;
    }
    score += curScore;
  }
  console.log(score);
});

로컬에선 정상작동하나 오답 처리된다.
혹시 map()이 문제인가 싶어 forEach()도 쓰고, 일반 for문도 써 봤다.

let fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

input.forEach((ques, idx) => {
  if (idx === 0) return;

  const quesArray = ques.split("");
  let curScore = 0;
  let score = 0;

  for (let j = 0; j < quesArray.length; j++) {
    if (quesArray[j] === "O") {
      curScore++;
    } else {
      curScore = 0;
    }
    score += curScore;
  }
  console.log(score);
});

let fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

for (let i = 0; i < input.length; i++) {
  if (i === 0) continue;
  let curScore = 0;
  let score = 0;

  for (let j = 0; j < input[i].length; j++) {
    if (input[i][j] === "O") {
      curScore++;
    } else {
      curScore = 0;
    }
    score += curScore;
  }
  console.log(score);
}

전부 오답 처리된다.

array.reduce()는 써도 된다.

다음 코드는 정답 처리되었다.

let fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");

let number = Number(input[0]);

for (let i = 1; i <= number; i++) {
  const arr = input[i].split("");
  let curScore = 0;

  const answer = arr.reduce((acc, cur, idx) => {
    if (cur === "O") {
      curScore++;
      return acc + curScore;
    } else {
      curScore = 0;
      return acc;
    }
  }, 0);
  console.log(answer);
}

정답 코드에서 reduce()를 쓸 만한 부분만 변형한 답이다.
사실 이 문제를 처음 딱 봤을 때 reduce()를 쓰기 좋은 문제라고 느꼈다. 통과가 돼서 다행이다.

결론. 첫 번째 라인에 받는 테스트케이스의 개수 데이터를 shift로 삭제하거나, 반복문을 통째로 돌리면서 첫번째 라인만 예외처리하는 식으로 코드를 짤 경우, 오답이 된다.

첫 라인에 받는 개수 데이터를 무시하지 말아야겠다..고 느꼈다.


그리고 오늘의 수확!
String 데이터에 첨자 연산자[]를 쓰면 각 문자에 접근할 수 있다는 점을 까마득히 잊고 있다가 이번 문제를 통해 깨달았다.