My Boundary As Much As I Experienced

우승자는 누구?(백준 코딩테스트 5179번, 구현/정렬, NodeJS 풀이) 본문

Algorithm/Coding Test

우승자는 누구?(백준 코딩테스트 5179번, 구현/정렬, NodeJS 풀이)

Bumang 2024. 3. 27. 13:32

https://www.acmicpc.net/problem/5179

 

 

문제 수준:

실버3

 

문제 요약:

문제 풀이 대회를 했는데, N명의 참가자가 M개의 문제를 풀게 됐다.

 

이때 점수가 존재하는데,

문제를 맞히면 '문제를 맞힌 시각 + (틀린 횟수 x 20)'을 점수로써 해당 참가자에게 주어진다.

(*맞춘 다음 다시 푼다고 하면 첫 풀이만 인정된다. 중복 반영은 안 된다.)

 

순위를 정할 땐

푼 문제 수가 많은 순으로 등수가 높고,

푼 문제 수가 같은 경우엔 점수가 낮은 쪽이 높다

(그렇다면 점수가 아니라 패널티 아닌가..)

 

이 테스트기를 구현하시오.

 

 

입출력 예 (입력 / 출력):

문제 풀이 전략:

뭐랄까. 점수라는건 사실 패널티라고 봐야할거 같다.

틀릴수록 점수가 오른다는 것이 직관적으로 안 받아들여지고,

점수가 높으면 안 좋은거라는 것에서 또 직관에 어긋나는 느낌이다.

이걸 패널티라고 바꾸면 나름 받아들여진다.

 

그리고 이런 구현 문제는 한 번의 로직으로 싹 다 해결하려고 하면 안 된다.

왜냐하면 문제에 딱히 고난이도의 알고리즘은 없더라도,

매우 복합적으로 신경 쓸 정책?들이 많기 때문이다.

로직 관심사 분리가 꼭 필요하다.

 

아래는 이번 문제를 풀기위해 짠 객체 구조이다.

플레이어마다 총점(result), 푼문제(solved), 문제별상태(오답횟수, 해결여부, 해결한 시각) 등등..

매우 많은 정보들을 기록하고, 이를 조합해서 패널티 점수를 계산해야되고...

이런 것들을 한 큐에 스크립트 줄줄 내려오면서 계산하는게 아니라 함수로 나눠놔야 한다. 

{
    '0': { // 테스트케이스 인덱스
      '1': { // 플레이어1
        result: 24, // 총점
        solved: 1, // 푼 문제 갯수
        A: { negative: 0, judge: false, solvedTime: 0 }, // 문제 별 상태: { 오답 횟수, 해결 여부, 해결한 시각 }
        B: { negative: 0, judge: true, solvedTime: 24 },
        C: { negative: 1, judge: false, solvedTime: 0 },
        D: { negative: 0, judge: false, solvedTime: 0 }
      },
      '2': { // 플레이어2
        result: 100,
        solved: 2,
        A: { negative: 0, judge: false, solvedTime: 0 },
        B: { negative: 0, judge: true, solvedTime: 55 },
        C: { negative: 1, judge: true, solvedTime: 25 },
        D: { negative: 0, judge: false, solvedTime: 0 }
      },
      '3': { // 플레이어3
        result: 55,
        solved: 1,
        A: { negative: 0, judge: false, solvedTime: 0 },
        B: { negative: 0, judge: false, solvedTime: 0 },
        C: { negative: 0, judge: false, solvedTime: 0 },
        D: { negative: 0, judge: true, solvedTime: 55 }
      }
    },
    '1' : {
        ...
    }
}

 

나는 이 문제를 로직을 3가지 함수로 분리하여 풀었다.

 

- setBoard : 보드의 뼈대를 만들고 초기값을 입력해놓는다. (테스트케이스가 초기화될때마다 실행된다)

- describeScore: 주어진 스크립트에 의거하여 점수와 풀이여부를 Board에 기입해놓는다.

- consoleScoreBoard: 마지막에 보드에 의거하여 최종 순위와 점수 등등을 console.log로 찍는다.

 

이런 식으로 함수를 나눠서 구현했다.

 

문제가 요구하는 알고리즘이 어렵진 않지만 그 갯수가 아주 여러개이며 서로 의존하고 있기 때문에

집중하지 않으면 분명 까먹는 정책이 있거나 잘못 이해한 경우가 발생할 것이다.

방심하면 시간 소모가 큰 문제가 될 것.

 

내 풀이:

let fs = require("fs");
let [n, ...cases] = fs.readFileSync("input.txt").toString().trim().split("\n");

let board = null; // 일단 null 상태지만 setTemplate 함수를 실행시키면 초기화 된다.
let index = 0; // 현재 테스트케이스를 가리킴

for (let i = 0; i < cases.length; i++) {
  const spl = cases[i].split(" ");
  if (spl.length === 3) { // 스크립트를 " "으로 나눈게 3개라는 것은 새로운 테스트케이스가 시작됐단 의미
    const [m, n, p] = spl;
    index++; // 인덱스를 올린다.
    setBoard(index, +p, +m); // setBoard 실행
  } else { // 새로운 테스트케이스가 아니라면
    const [player, prob, time, judge] = spl;
    describeScore(player, prob, +time, +judge); // 각 플레이어의 액션을 보드에 기입
  }
}

consoleScoreBoard(); // 위 for문 다 돌았으면 consoleScoreBoard를 실행

function setBoard(index, player, matter) {
  const converter = { // 인덱스 -> 문제 이름 컨버터..
    0: "A",
    1: "B",
    2: "C",
    3: "D",
    4: "E",
    5: "F",
    6: "G",
    7: "H",
    8: "I",
    9: "J",
  };

  if (!board) { // 보드가 null일 때,
    board = {}; // 보드 초기화
  }
  board[index] = {}; // board의 index(테스트케이스의 index값)로 객체 생성
  for (let i = 0; i < player; i++) {
    board[index][i + 1] = {}; // 플레이어 객체 생성
    board[index][i + 1].result = 0; // 플레이어의 총점
    board[index][i + 1].solved = 0; // 플레이어의 푼문제 갯수
    for (let k = 0; k < matter; k++) {
      board[index][i + 1][converter[k]] = { // 문제 생성
        negative: 0, // 해당 문제의 오답 횟수
        judge: false, // 해당 문제의 해결 여부
        solvedTime: 0, // 해당 문제의 해결한 시각
      };
    }
  }
}

function consoleScoreBoard() { // 순위 출력 함수
  for (let i = 1; i <= +n; i++) {
    console.log(`Data Set ${i}:`);
    const target = Object.entries(board[i]).sort((a, b) => { // 정렬 로직
      if (b[1].solved > a[1].solved) { // 푼 문제 횟수 비교
        return 1;
      } else if (b[1].solved === a[1].solved) { // 만약 푼 문제가 같다면
        if (b[1].result > a[1].result) { // 총점(패널티)가 누가 더 큰지?
          return -1;
        } else {
          return 1;
        }
      } else {
        return -1;
      }
    });

    for (let j = 0; j < target.length; j++) {
      const player = target[j][0];
      const solved = target[j][1].solved;
      const score = target[j][1].result;

      console.log(`${player} ${solved} ${score}`); // 플레이어들의 성적 출력
    }
    console.log(); // 띄어쓰기
  }
}

function describeScore(player, prob, time, judge) { // 플레이어들의 성적을 기입하는 곳
  if (judge && !board[index][player][prob].judge) { // 풀었으며, 풀이 여부가 아직 false라면
    board[index][player][prob].judge = true; // 풀이여부는 true로 전환
    board[index][player][prob].solvedTime = time; // 해결 시간 설정
    board[index][player].result += +time + board[index][player][prob].negative * 20; // 점수 업데이트(패널티)
    board[index][player].solved++; // 푼 문제 1 증가
  } else {
    board[index][player][prob].negative++; // 오답인 경우 오답횟수만 증가
  }
}