Algorithm with Math 1
중복순열 문제
rockPaperScissors
문제
가위바위보 게임은 2인 이상의 사람이 동시에 ‘가위, 바위, 보’를 외치고 동시에 가위, 바위 또는 보 중에서 한 가지를 의미하는 손 모양을 내밀어 승부를 결정짓는 게임입니다. 세 판의 가위바위보 게임을 할 경우, 한 사람은 세 번의 선택(예. 가위, 가위, 보)을 할 수 있습니다. 세 번의 선택으로 가능한 모든 경우의 수를 구하는 함수를 작성합니다.
입력
- 없음
출력
- 2차원 배열(
arr[i]
)을 리턴해야 합니다. arr[i]
는 전체 경우의 수 중 한 가지 경우(총 세 번의 선택)를 의미하는 배열입니다.arr[i]
는'rock'
,'paper'
,'scissors'
중 한 가지 이상을 요소로 갖는 배열입니다.arr[i].length
는 3
주의사항
- 최종적으로 리턴되는 배열의 순서는 가중치 적용 정렬(Weighted Sort)을 따릅니다.
- 중요도는
'rock'
,'paper'
,'scissors'
순으로 높습니다. - 쉽게 생각해 올림픽 순위 결정 방식을 참고하면 됩니다.
- 금메달(
'rock'
)이 은메달('paper'
)보다 우선하고, 은메달('paper'
)이 동메달('scissors'
)보다 우선합니다.
입출력 예시
let output = rockPaperScissors();
console.log(output);
/*
[
["rock", "rock", "rock"],
["rock", "rock", "paper"],
["rock", "rock", "scissors"],
["rock", "paper", "rock"],
// ...etc ...
]
*/
Advanced
- 가위바위보 게임의 수를 나타내는 양의 정수 rounds가 주어질 경우, 해당 rounds 동안 선택할 수 있는 모든 경우의 수를 리턴하도록 함수를 작성해 보세요.
let output = rockPaperScissors(5);
console.log(output);
/*
[
["rock", "rock", "rock", "rock", "rock"],
["rock", "rock", , "rock", "rock", "paper"],
["rock", "rock", , "rock", "rock", "scissors"],
["rock", "rock", "rock", "paper", "rock"],
["rock", "rock", "rock", "paper", "paper"],
["rock", "rock", "rock", "paper", "scissors"],
["rock", "rock", "rock", "scissors", "rock"],
// ...etc ...
]
*/
const rockPaperScissors = function (rounds) {
// rounds 매개변수를 임의로 넣습니다.
// rounds 변수가 있을 경우 그대로 사용하고, 없다면 3(기본)을 사용합니다.
rounds = rounds || 3;
const rps = ['rock', 'paper', 'scissors'];
// 결과를 담을 배열 선언
const outcomes = [];
// 재귀를 사용할 함수 선언
// rounds를 넣을 변수 roundsToGo, 일회용 배열인 playedSoFar 변수를 선언합니다.
// 재귀를 사용하는 이유는, 배열의 모든 요소의 경우의 수를 훑기 위한 적절한 방법이기 때문입니다.
// 간단히 말하자면, 이 함수는 rounds 숫자를 기준으로, 일회용 배열에 rps 요소를 rounds 숫자만큼 넣게 됩니다.
// 이 로직을 잘 이해할 수 있을 때까지 하단의 함수 로직을 연구해야 합니다.
let permutate = function (roundsToGo, playedSoFar) {
// rounds가 0일 경우 outcones 배열에 삽입하고, 재귀에서 빠져나옵니다.
if (roundsToGo === 0) {
outcomes.push(playedSoFar);
return;
}
// rps 배열을 한 번씩 순회합니다.
for (let i = 0; i < rps.length; i++) {
// rps의 i번째 요소를 변수에 담아서
let currentPlay = rps[i];
// permutate(본인)에 기존 rounds에서 하나 뺀 숫자와, 일회용 배열 playedSoFar에 currentPlay를 삽입하여 재귀를 실행합니다.
// rounds에서 하나를 빼는 이유는, 일회용 배열의 크기를 rounds만큼 맞춰주기 위함입니다. [rock, rock, rock]
// Q. playedSoFar.push(currentPlay)로 할 수 있을 텐데, 왜 concat을 사용할까요? push는 배열의 길이를 리턴값으로 가진다 반면에 concat의 리턴값은 새로운 배열을 반환한다.
permutate(roundsToGo - 1, playedSoFar.concat(currentPlay));
/**
* 이 재귀의 로직은 이렇습니다. 처음 실행된 반복문은 rps를 전부 순회해야 끝이 납니다.
* 그러나 한 번 반복할 때마다 permutate 함수가 실행되고, rounds의 숫자는 짧아지며, playedSoFar에 요소가 계속 쌓일 것입니다.
* 결국, roundsToGo가 0이 될 때까지 이 반복문은 rps[i]가 0일 것이며, playedSoFar에는 [rock, rock, rock]이 되어 outcones에 Push하고, 종료하게 됩니다.
* return이 되었으니, 한 번의 재귀 호출이 끝났습니다. 마지막 호출 바로 전으로 돌아가,
* for문은 i = 1을 가리키게 될 것이고, [rock, rock, paper]을 삽입한 뒤 호출을 하게 됩니다.
* roundsToGo가 0이 되어, 해당 배열은 outcomes 배열에 삽입됩니다.
* 이런 식으로 모든 호출의 반복문이 끝날 때까지 반복하며 outcomes에 경우의 수 요소들이 담기게 됩니다.
*/
}
};
// 함수를 실행합니다.
permutate(rounds, []);
// outcomes를 반환합니다.
return outcomes;
};
rockPaperScissors(4);
result = [
[ 'rock', 'rock', 'rock', 'rock' ],
[ 'rock', 'rock', 'rock', 'paper' ],
[ 'rock', 'rock', 'rock', 'scissors' ],
[ 'rock', 'rock', 'paper', 'rock' ],
[ 'rock', 'rock', 'paper', 'paper' ],
[ 'rock', 'rock', 'paper', 'scissors' ],
[ 'rock', 'rock', 'scissors', 'rock' ],
[ 'rock', 'rock', 'scissors', 'paper' ],
[ 'rock', 'rock', 'scissors', 'scissors' ],
[ 'rock', 'paper', 'rock', 'rock' ],
[ 'rock', 'paper', 'rock', 'paper' ],
[ 'rock', 'paper', 'rock', 'scissors' ],
[ 'rock', 'paper', 'paper', 'rock' ],
[ 'rock', 'paper', 'paper', 'paper' ],
[ 'rock', 'paper', 'paper', 'scissors' ],
[ 'rock', 'paper', 'scissors', 'rock' ],
[ 'rock', 'paper', 'scissors', 'paper' ],
[ 'rock', 'paper', 'scissors', 'scissors' ],
[ 'rock', 'scissors', 'rock', 'rock' ],
[ 'rock', 'scissors', 'rock', 'paper' ],
[ 'rock', 'scissors', 'rock', 'scissors' ],
[ 'rock', 'scissors', 'paper', 'rock' ],
[ 'rock', 'scissors', 'paper', 'paper' ],
[ 'rock', 'scissors', 'paper', 'scissors' ],
[ 'rock', 'scissors', 'scissors', 'rock' ],
[ 'rock', 'scissors', 'scissors', 'paper' ],
[ 'rock', 'scissors', 'scissors', 'scissors' ],
[ 'paper', 'rock', 'rock', 'rock' ],
[ 'paper', 'rock', 'rock', 'paper' ],
[ 'paper', 'rock', 'rock', 'scissors' ],
[ 'paper', 'rock', 'paper', 'rock' ],
[ 'paper', 'rock', 'paper', 'paper' ],
[ 'paper', 'rock', 'paper', 'scissors' ],
[ 'paper', 'rock', 'scissors', 'rock' ],
[ 'paper', 'rock', 'scissors', 'paper' ],
[ 'paper', 'rock', 'scissors', 'scissors' ],
[ 'paper', 'paper', 'rock', 'rock' ],
[ 'paper', 'paper', 'rock', 'paper' ],
[ 'paper', 'paper', 'rock', 'scissors' ],
[ 'paper', 'paper', 'paper', 'rock' ],
[ 'paper', 'paper', 'paper', 'paper' ],
[ 'paper', 'paper', 'paper', 'scissors' ],
[ 'paper', 'paper', 'scissors', 'rock' ],
[ 'paper', 'paper', 'scissors', 'paper' ],
[ 'paper', 'paper', 'scissors', 'scissors' ],
[ 'paper', 'scissors', 'rock', 'rock' ],
[ 'paper', 'scissors', 'rock', 'paper' ],
[ 'paper', 'scissors', 'rock', 'scissors' ],
[ 'paper', 'scissors', 'paper', 'rock' ],
[ 'paper', 'scissors', 'paper', 'paper' ],
[ 'paper', 'scissors', 'paper', 'scissors' ],
[ 'paper', 'scissors', 'scissors', 'rock' ],
[ 'paper', 'scissors', 'scissors', 'paper' ],
[ 'paper', 'scissors', 'scissors', 'scissors' ],
[ 'scissors', 'rock', 'rock', 'rock' ],
[ 'scissors', 'rock', 'rock', 'paper' ],
[ 'scissors', 'rock', 'rock', 'scissors' ],
[ 'scissors', 'rock', 'paper', 'rock' ],
[ 'scissors', 'rock', 'paper', 'paper' ],
[ 'scissors', 'rock', 'paper', 'scissors' ],
[ 'scissors', 'rock', 'scissors', 'rock' ],
[ 'scissors', 'rock', 'scissors', 'paper' ],
[ 'scissors', 'rock', 'scissors', 'scissors' ],
[ 'scissors', 'paper', 'rock', 'rock' ],
[ 'scissors', 'paper', 'rock', 'paper' ],
[ 'scissors', 'paper', 'rock', 'scissors' ],
[ 'scissors', 'paper', 'paper', 'rock' ],
[ 'scissors', 'paper', 'paper', 'paper' ],
[ 'scissors', 'paper', 'paper', 'scissors' ],
[ 'scissors', 'paper', 'scissors', 'rock' ],
[ 'scissors', 'paper', 'scissors', 'paper' ],
[ 'scissors', 'paper', 'scissors', 'scissors' ],
[ 'scissors', 'scissors', 'rock', 'rock' ],
[ 'scissors', 'scissors', 'rock', 'paper' ],
[ 'scissors', 'scissors', 'rock', 'scissors' ],
[ 'scissors', 'scissors', 'paper', 'rock' ],
[ 'scissors', 'scissors', 'paper', 'paper' ],
[ 'scissors', 'scissors', 'paper', 'scissors' ],
[ 'scissors', 'scissors', 'scissors', 'rock' ],
[ 'scissors', 'scissors', 'scissors', 'paper' ],
[ 'scissors', 'scissors', 'scissors', 'scissors' ]
🔑 for (let i = 0; i < rps.length; i++) {
let currentPlay = rps[i];
permutate(roundsToGo - 1, playedSoFar.concat(currentPlay));
// 위의 세줄의 코드는 사실상 rps.length 만큼 for 문을 rps.length 만큼 중첩시킨 코드와 같다고 할 수 있다.
// 재귀적으로 이해하고 구현한다면 간결하게 구현이 가능하다!