개발하고 싶은 초심자
Algorithm with Math Coplit(5-6) 본문
5. [중복순열] 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')보다 우선합니다.
Advanced
- 가위바위보 게임의 수를 나타내는 양의 정수 rounds가 주어질 경우, 해당 rounds 동안 선택할 수 있는 모든 경우의 수를 리턴하도록 함수를 작성해 보세요.
let output = rockPaperScissors();
console.log(output);
/*
[
["rock", "rock", "rock"],
["rock", "rock", "paper"],
["rock", "rock", "scissors"],
["rock", "paper", "rock"],
// ...etc ...
]
*/
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 ...
]
*/
// Advanced가 포함된 레퍼런스 코드입니다.
const rockPaperScissors = function (rounds) {
// rounds 매개변수를 임의로 넣습니다.
// rounds 변수가 있을 경우 그대로 사용하고, 없다면 3(기본)을 사용합니다.
rounds = rounds || 3;
const rps = ['rock', 'paper', 'scissors'];
// 결과를 담을 배열 선언
const result = [];
// 재귀를 사용할 함수 선언
// rounds를 넣을 변수 roundsToGo, 일회용 배열인 playedSoFar 변수를 선언합니다.
// 재귀를 사용하는 이유는, 배열의 모든 요소의 경우의 수를 훑기 위한 적절한 방법이기 때문입니다.
// 간단히 말하자면, 이 함수는 rounds 숫자를 기준으로,
// 일회용 배열에 rps 요소를 rounds 숫자만큼 넣게 됩니다.
// 이 로직을 잘 이해할 수 있을 때까지 하단의 함수 로직을 연구해야 합니다.
let permutate = function (roundsToGo, playedSoFar) {
// rounds가 0일 경우 result 배열에 삽입하고, 재귀에서 빠져나옵니다.
if (roundsToGo === 0) {
result.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을 사용할까요?
permutate(roundsToGo - 1, playedSoFar.concat(currentPlay));
/**
* 이 재귀의 로직은 이렇습니다. 처음 실행된 반복문은 rps를 전부 순회해야 끝이 납니다.
* 그러나 한 번 반복할 때마다 permutate 함수가 실행되고, rounds의 숫자는 짧아지며,
* playedSoFar에 요소가 계속 쌓일 것입니다.
* 결국, roundsToGo가 0이 될 때까지 이 반복문은 rps[i]가 0일 것이며,
* playedSoFar에는 [rock, rock, rock]이 되어
* result에 Push하고, 종료하게 됩니다.
* return이 되었으니, 한 번의 재귀 호출이 끝났습니다.
* 마지막 호출 바로 전으로 돌아가,
* for문은 i = 1을 가리키게 될 것이고, [rock, rock, paper]을 삽입한 뒤 호출을 하게 됩니다.
* roundsToGo가 0이 되어, 해당 배열은 result 배열에 삽입됩니다.
* 이런 식으로 모든 호출의 반복문이 끝날 때까지 반복하며
* result의 경우의 수 요소들이 담기게 됩니다.
*/
}
};
// 함수를 실행합니다.
permutate(rounds, []);
// result를 반환합니다.
return result;
};
6. [순열] 새로운 치킨 소스 레시피
개업 이래로 항상 승승장구하는 '승승장구 치킨집'의 비결은 소스에 있다. 수많은 타사 브랜드 치킨집들이 승승장구 치킨집의 소스 비결을 알아내려고 했으나 빈번히 포기했다. 그 이유는 5대째 내려오는 '비밀의 승승장구 치킨 소스 비율 레시피'는 70억 인구 중 사장님만 알고 있기 때문이다. 최근, 누리꾼 사이에서 이 레시피의 일부분을 발췌했다는 소문을 듣게 되었다. 그 소문은 다음과 같다.
- N 가지의 재료 중에 단 M 가지만을 사용하여 조합한 모든 경우의 수 중 하나이다.
- 재료는 0과 1로만 이루어진 숫자로 암호화가 되어 있고, 항상 1로 시작하며 복호화를 할 수 없다.
- 단, 0이 3개 이상인 재료는 상한 재료이기 때문에 제외한다.
- 재료의 순서에 따라 맛이 달라지기 때문에, 재료를 넣는 순서가 다르다면 다른 레시피이다.
이 소문을 참고하여 '비밀의 승승장구 치킨 소스'가 될 수 있는 경우의 수를 모두 반환하는 함수를 작성하세요.
입력
인자 1: stuffArr
- Number 타입의 재료를 담은 배열
- 요소는 0과 1로만 이루어진 숫자이며, 항상 1로 시작합니다.
- 요소는 중복될 수 없습니다.
- 요소의 길이는 20 이하입니다.
- 배열의 길이는 2 이상 10 이하입니다.
- ex) [111, 110, 1010, 10, 10110]
인자 2: choiceNum
- Number 타입의 1 이상 stuffArr 길이 이하의 자연수
- 재료를 선택할 수 있는 수를 뜻합니다.
- ex) 2
출력
- Number 타입을 반환해야 합니다.
- stuffArr가 [1, 10, 11000, 1111] 이고, choiceNum은 2라면 사용 가능한 재료는 [1, 10, 1111] 입니다. 조합할 수 있는 경우의 수는 6 가지입니다.
주의사항
- 만약, 주어진 재료 모두 사용할 수 없다면 빈 배열[]을 반환해야 합니다.
- 만약, 사용할 수 있는 재료가 choiceNum보다 작다면 빈 배열[] 을 반환해야 합니다.
- 조합 및 요소는 작은 숫자 -> 큰 숫자로 정렬합니다.
- 예시로 [1, 10, 11000, 1111]이 요소로 들어왔다면, 0이 세 개인 11000을 제외하고 [1, 10, 1111] 순서가 되어야 하며, [ [1, 10], [1, 1111], [10, 1], [10, 1111], [1111, 1], [1111, 10] ]을 반환해야 합니다.
입출력 예시
const output1 = newChickenRecipe([1, 10, 1100, 1111], 2);
console.log(output1);
/*
[
[1, 10], [1, 1100], [1, 1111],
[10, 1], [10, 1100], [10, 1111],
[1100, 1], [1100, 10], [1100, 1111],
[1111, 1], [1111, 10], [1111, 1100]
];
*/
const output2 = newChickenRecipe([10000, 10, 1], 3);
console.log(output2); // []
const output3 = newChickenRecipe([11, 1, 10, 1111111111, 10000], 4);
console.log(output3);
/*
[
[1, 10, 11, 1111111111],
[1, 10, 1111111111, 11],
[1, 11, 10, 1111111111],
[1, 11, 1111111111, 10],
[1, 1111111111, 10, 11],
[1, 1111111111, 11, 10],
[10, 1, 11, 1111111111],
[10, 1, 1111111111, 11],
[10, 11, 1, 1111111111],
[10, 11, 1111111111, 1],
[10, 1111111111, 1, 11],
[10, 1111111111, 11, 1],
[11, 1, 10, 1111111111],
[11, 1, 1111111111, 10],
[11, 10, 1, 1111111111],
[11, 10, 1111111111, 1],
[11, 1111111111, 1, 10],
[11, 1111111111, 10, 1],
[1111111111, 1, 10, 11],
[1111111111, 1, 11, 10],
[1111111111, 10, 1, 11],
[1111111111, 10, 11, 1],
[1111111111, 11, 1, 10],
[1111111111, 11, 10, 1],
]
*/
function newChickenRecipe(stuffArr, choiceNum) {
// 상한 재료 필터링
// 새로운 배열로 리턴
// 필터링해서 나와야하는 것은 0이 3개인 요소를 제외한 배열
let freshArr = stuffArr.filter((el) => String(el).slice(-3) !== '000');
const recur = function(arr, choiceNum) {
let result = [];
if(choiceNum === 1) return arr.map((hand) => [hand]);
// 재귀함수는 중복 순열과 거의 동일하다
arr.forEach((hand, idx, arr) => {
const fixer = hand;
// 일반 순열은 이미 선택한 요소는 다시 선택할 수 없다
// 필터링한 배열을 재귀의 인자로 전달한다
const restArr = arr.filter((_, index) => index !== idx)
const permuationArr = recur(restArr, choiceNum - 1)
const combineFixer = permuationArr.map((hand) =>
[fixer, ...hand])
result.push(...combineFixer)
})
return result;
}
return recur(freshArr, choiceNum);
}
// reference code
function newChickenRecipe(stuffArr, choiceNum) {
// stuffArr에서 0이 3개 이상이라면 전부 필터로 거르기.
let freshArr = [];
for (let i = 0; i < stuffArr.length; i++) {
const element = String(stuffArr[i])
.split('')
.filter((e) => e === '0');
if (element.length <= 2) {
freshArr.push(stuffArr[i]);
}
}
// 정렬
freshArr.sort((a, b) => a - b);
// 엣지 케이스 처리
if (freshArr.length === 0 || freshArr.length < choiceNum) return [];
// 레시피 초기화
let result = [];
// freshArr를 상대로 순열 구하기
const permutation = (arr, bucket, n) => {
if (n === 0) {
result.push(bucket);
return;
}
for (let i = 0; i < arr.length; i++) {
// 하나를 초이스함
const choice = arr[i];
// 배열을 슬라이스함
const sliceArr = arr.slice();
// 초이스만 뺀다
sliceArr.splice(i, 1);
// 재귀
permutation(sliceArr, bucket.concat(choice), n - 1);
}
};
// 실행
permutation(freshArr, [], choiceNum);
// 순열의 길이 반환
return result;
}
'기술개념정리(in Javascript)' 카테고리의 다른 글
220303 D+52 Algorithm with math(순열 / 조합, GCD / LCM, 멱집합), 정규표현식 (0) | 2022.03.03 |
---|---|
Algorithm with Math Coplit 9(Advanced 멱집합) (0) | 2022.03.03 |
220302 D+51 Algorithm, Time Complexity(시간 복잡도), greedy Algorithm, Implementation (0) | 2022.03.02 |
Greedy Algorithm Coplit (0) | 2022.03.02 |
220228 D+50 사용 권한, 환경 변수 (0) | 2022.02.28 |
Comments