슈퍼 게임 개발자 오렐리는 큰 고민에 빠졌다. 그녀가 만든 프랜즈 오천성이 대성공을 거뒀지만, 요즘 신규 사용자의 수가 급감한 것이다. 원인은 신규 사용자와 기존 사용자 사이에 스테이지 차이가 너무 큰 것이 문제였다.
이 문제를 어떻게 할까 고민 한 그녀는 동적으로 게임 시간을 늘려서 난이도를 조절하기로 했다. 역시 슈퍼 개발자라 대부분의 로직은 쉽게 구현했지만, 실패율을 구하는 부분에서 위기에 빠지고 말았다. 오렐리를 위해 실패율을 구하는 코드를 완성하라.
- 실패율은 다음과 같이 정의한다.
- 스테이지에 도달했으나 아직 클리어하지 못한 플레이어의 수 / 스테이지에 도달한 플레이어 수
전체 스테이지의 개수 N, 게임을 이용하는 사용자가 현재 멈춰있는 스테이지의 번호가 담긴 배열 stages가 매개변수로 주어질 때, 실패율이 높은 스테이지부터 내림차순으로 스테이지의 번호가 담겨있는 배열을 return 하도록 solution 함수를 완성하라.
1
이상 500
이하의 자연수이다.1
이상 200,000
이하이다.1
이상 N + 1
이하의 자연수가 담겨있다.N + 1
은 마지막 스테이지(N 번째 스테이지) 까지 클리어 한 사용자를 나타낸다.0
으로 정의한다.N | stages | result |
---|---|---|
5 | [2, 1, 2, 6, 2, 4, 3, 3] | [3,4,2,1,5] |
4 | [4,4,4,4,4] | [4,1,2,3] |
// 처음 답안 - 시간 초과
function solution(N, stages) {
let allLoseRate = [];
for (let i=1; i<=N; i++) {
let loseCount = 0;
let stageCount = 0;
for (let j=0; j<stages.length; j++) {
if (stages[j] >= i) stageCount++
if (stages[j] <= i) {
loseCount++;
stages.splice(j,1)
j--;
}
}
allLoseRate.push({idx:i, rate:(loseCount/stageCount)})
}
allLoseRate.sort((a,b) => b.rate - a.rate);
return allLoseRate.map(v=>v.idx);
}
splice()
를 사용하면 원본 배열의 원소가 삭제되고 배열의 길이가 변하기 때문에 splice()
함수를 사용해서 원소 1개를 삭제한 후에는 배열의 index를 참조하는 i의 값을 하나 감소시켜야 한다.
해당 코드에서는 splice()
이후 j--
를 추가하였다.
시간 초과가 떴다. 그 이유를 추측해보면 다음 두 가지로 추론할 수 있다.
-> 중첩 for문을 제거하고, splice()를 사용하지 않은 방법을 새로 작성하였다.
// 수정 답안
function solution(N, stages) {
let failureRates = [];
for (let i=1; i<=N; i++) {
let totalPlayer = stages.filter(stage => stage >= i).length;
// 도달한 스테이지가 i 이상인 요소들의 개수 == 해당 스테이지에 도달한 플레이어 수
let failed = stages.filter(stage => stage == i).length;
// 현재 스테이지와 일치하는 요소의 개수 == 해당 스테이지에서 실패한 플레이어 수
let failureRate = failed / totalPlayer;
failureRates.push({stage:i, rate:failureRate})
// 스테이지 번호와 실패율을 함께 추가함
}
failureRates.sort((a,b) => b.rate - a.rate);
// 내림차순 정렬
return failureRates.map(v=>v.stage);
// 실패율을 기준으로 내림차순 정렬한 전체 실패율 배열에서 스테이지 번호만 리턴.
}
다만 이 문제는 totalPlayer가 0일 때가 있을 수 있는데, 그때의 처리를 해주지 않으면 분모가 0이 되는 경우가 발생할 수 있다. Javascript에서는 0으로 나눌 때 Infinity
를 반환하는데, 이것은 실패율을 계산할 때 문제를 발생시키거나 정렬 로직을 예쌍하지 못한 결과로 만들 수 있으므로 분모가 0일 때를 방지하는 삼항연산자를 추가하였다.
// 최종 답안
function solution(N, stages) {
let failureRates = [];
for (let i=1; i<=N; i++) {
let totalPlayer = stages.filter(stage => stage >= i).length;
// 도달한 스테이지가 i 이상인 요소들의 개수 == 해당 스테이지에 도달한 플레이어 수
let failed = stages.filter(stage => stage == i).length;
// 현재 스테이지와 일치하는 요소의 개수 == 해당 스테이지에서 실패한 플레이어 수
let failureRate = totalPlayer === 0 ? 0 : failed / totalPlayer;
// 도달한 플레이어 수가 0이면 실패율은 0(분모가 0이 되는 걸 방지), 아닐 경우 실패율 계산
failureRates.push({stage:i, rate:failureRate})
// 스테이지 번호와 실패율을 함께 추가함
}
failureRates.sort((a,b) => b.rate - a.rate);
// 내림차순 정렬
return failureRates.map(v=>v.stage);
// 실패율을 기준으로 내림차순 정렬한 전체 실패율 배열에서 스테이지 번호만 리턴.
}
function solution(N, stages) {
let allLoseRate = [];
let total = stages.length;
for (let i=1; i<=N; i++) {
let player = stages.filter(v=> v=== i).length;
let failRate = 0;
if (player === 0) failRate = 0;
else failRate = (player) / total;
total -= player;
allLoseRate.push({idx:i, rate:(failRate)})
}
allLoseRate.sort((a,b) => b.rate - a.rate);
return allLoseRate.map(v=>v.idx);
}
비슷한 방식이지만 해당 스테이지에 도달한 모든 플레이어 수를 구할 때 미리 stages의 전체 크기를 total에 할당하고, 반복문을 돌리면서 현재 스테이지에서 실패한 플레이어 수를 total에서 빼주는 방식을 사용하였다. 반복문에서 totalPlayer를 구하기 위한 filter()를 제거할 수 있으므로 내 코드보다 더 효율적인 코드라고 생각한다.