문제설명
문제분석
이 문제는 모바일 키패드를 누르는 문제입니다. 정확히는 눌러야하는 번호에 따라 가까운 위치의 손을 기록하는 문제입니다. 왼쪽의 1, 4, 7은 왼손으로, 오른쪽의 3, 6, 9는 오른손으로 누르며, 가운데 2, 5, 8, 0은 두 손 중 가까운 손이 누릅니다. 이때 두 손의 거리가 같다면, 오른손잡이인지 왼손잡이인지에 따라 누르는 손이 결정됩니다. 거리는 키패드의 상하좌우를 한칸 이동할 때 1씩 증가하며, 가운데 번호를 누를 때에는 반드시 현재 손가락의 위치부터 눌러야하는 번호까지의 거리를 구해야 합니다. 따라서 현재 손가락의 위치를 기억하고 있어야 합니다. 문제가 조금 복잡한 관계로 풀어서 정리하겠습니다.
- 키패드는 가로 3, 세로 4로 정해져 있다.
- 키패드의 왼쪽 3개 숫자 1, 4, 7은 항상 왼손으로, 오른쪽 3개 숫자 3, 6, 9는 항상 오른손으로 누른다.
- 왼손과 오른손은 각각 *과 # 위치에서 출발한다.
- numbers의 요소에 따라 손가락을 움직이고, 키패드 가운데의 2, 5, 8, 0은 거리가 가까운 손으로 누른다.
- 이때 오른손과 왼손으로부터 눌러야할 번호와 각각의 거리가 같다면, 오른손잡이라면 오른손으로, 왼손잡이라면 왼손으로 누른다.
- 번호를 누르면 항상 position을 업데이트 한다.
문제를 단순화 했으니 이해하기 쉽게 코드를 짜보겠습니다.
이해하기 쉽게 코드작성하기
일단 되는대로 작성했습니다.
function solution(numbers, hand) {
// constant
const l = "L", r = "R";
// 가로 index
const rowArr = [0, 1, 2, 3];
// 세로 index
const colArr = [0, 1, 2];
// 각 손으로 누를 번호
const alwaysLeftHand = [1, 4, 7];
const alwaysRightHand = [3, 6, 9];
// 오른손잡이인지 왼손잡이인지 분류
const sameLengthHand = hand === "right" ? r : l;
// 최초 시작 좌표, [0, 0]은 키패드 1
let currentLeftHandPosition = [3, 0],
currentRightHandPosition = [3, 2];
return numbers.reduce((a, c) => {
const idxForL = alwaysLeftHand.indexOf(c);
const idxForR = alwaysRightHand.indexOf(c);
// 누를 번호가 가운데에 있다면
if(idxForL === -1 && idxForR === -1) {
// 타겟(눌러야 하는 번호)의 좌표
const target = getTarget(c);
// 각 손으로부터 타겟까지의 거리
const lenForL = getLength(target, currentLeftHandPosition);
const lenForR = getLength(target, currentRightHandPosition);
// 거리가 같다면 오른손잡이인지 왼손잡이인지 판별
if(lenForL === lenForR) {
a += sameLengthHand;
if(sameLengthHand === l) {
currentLeftHandPosition = target;
} else {
currentRightHandPosition = target;
}
} else if(lenForL < lenForR) {
a += l;
currentLeftHandPosition = target;
} else {
a += r;
currentRightHandPosition = target;
}
// 누를 번호가 왼손쪽이라면
} else if(idxForL > -1) {
a += l;
currentLeftHandPosition = [rowArr[idxForL], 0];
// 누를 번호가 오른손 쪽이라면
} else if(idxForR > -1) {
a += r;
currentRightHandPosition = [rowArr[idxForR], 2];
}
return a;
}, "");
}
// 타겟의 좌표를 리턴
function getTarget(num) {
const target = [0, 0];
const pad = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
["*", 0, "#"],
];
for(let r = 0; r < pad.length; r++) {
for(let c = 0; c < pad[r].length; c++) {
if(pad[r][c] === num) {
target[0] = r;
target[1] = c;
break;
}
}
}
return target;
}
// 타겟과 현재 위치의 거리를 리턴
function getLength(target, currentPosition) {
if (target[0] === currentPosition[0] && target[1] === currentPosition[1]) return 0;
let len = 0;
const cur = [...currentPosition];
for(let r = 0; r < 3; r++) {
if(target[0] === cur[0]) break;
cur[0] = target[0] < cur[0] ? cur[0] - 1 : cur[0] + 1;
len++;
}
for(let c = 0; c < 3; c++) {
if(target[1] === cur[1]) break;
cur[1] = target[1] < cur[1] ? cur[1] - 1 : cur[1] + 1;
len++;
}
return len;
}
코드 설명
일단 깁니다. 정신이 없습니다. 그 이유는 일단 돌아가게 만들었기 때문입니다. 이제 코드를 이쁘고 깔끔하게 정리해보겠습니다. 일단 쉬운 거 부터 하겠습니다.
getLength 함수는 거리를 구하는 함수입니다. 상하좌우에 따라 두 좌표의 거리를 구합니다. 두 좌표의 거리를 구할 때 항상 수평 또는 수직으로만 움직이므로, 두 좌표의 수평과 수직의 차이를 각각 구해 더하는 것으로도 계산할 수 있습니다. 변경된 getLength는 다음과 같습니다.
function getLength(target, cur, currentPosition) {
return Math.abs(target[0] - cur[0]) + Math.abs(target[1] - cur[1]);
}
왼손과 오른손으로만 눌러야하는 부분에서, 항상 양손 다 검사하고 있습니다. 이 부분을 먼저 확인 하고 종료하도록 변경해보겠습니다.
function solution(numbers, hand) {
const l = "L", r = "R";
const alwaysLeftHand = [1, 4, 7];
const alwaysRightHand = [3, 6, 9];
const sameLengthHand = hand === "right" ? r : l;
let currentLeftHandPosition = [3, 0],
currentRightHandPosition = [3, 2];
return numbers.reduce((a, c) => {
const target = getPosition(c);
if(alwaysLeftHand.includes(c)) {
a += l;
currentLeftHandPosition = target;
} else if (alwaysRightHand.includes(c)) {
a += r;
currentRightHandPosition = target;
} else {
const lenForL = getLength(target, currentLeftHandPosition);
const lenForR = getLength(target, currentRightHandPosition);
if(lenForL === lenForR) {
a += sameLengthHand;
if(sameLengthHand === l) {
currentLeftHandPosition = target;
} else {
currentRightHandPosition = target;
}
} else if(lenForL < lenForR) {
a += l;
currentLeftHandPosition = target;
} else {
a += r;
currentRightHandPosition = target;
}
}
return a;
}, "");
}
function getPosition(num) {
const target = [0, 0];
const pad = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
["*", 0, "#"],
];
for(let r = 0; r < pad.length; r++) {
for(let c = 0; c < pad[r].length; c++) {
if(pad[r][c] === num) {
target[0] = r;
target[1] = c;
break;
}
}
}
return target;
}
function getLength(target, cur) {
return Math.abs(target[0] - cur[0]) + Math.abs(target[1] - cur[1]);
}
매번 현재 위치의 좌표를 구하는 걸로 변경해볼까 했으나 현재 위치를 기록하고 활용하는 것이 더 이득인 것 같아 여기서 마무리했습니다.
굳이 코드를 짧게 하자면 getPosition에서 pad를 한 줄에 보이게 한다거나, alwaysLeftHand와 같이 짧은 배열을 그냥 배열 그 자체로 대체하는 방법이 있습니다. 그러나 getPosition의 pad는 개발자가 보기 편하라고 일부러 분할해둔 것이고, alwaysLeftHand와 같은 배열은 반복문 안에서 매번 새롭게 선언하는 것보다, 한 번 선언과 할당을 해두고 여러번 사용하는 것이 이득이라고 생각했습니다.
물론 코드 관리 면에서도 상단에서 기록해두는 게 용이합니다. 만약 상황이 달라져서 왼손과 오른손으로 누를 수 있는 범위가 달라진다면 언제든지 코드 상단만 수정하여 같은 코드를 사용할 수 있기 때문입니다.
'Practice for coding test' 카테고리의 다른 글
프로그래머스 - Level 2. 예상 대진표 / JavaScript (js) (0) | 2023.02.21 |
---|---|
프로그래머스 - Level 2. 멀리 뛰기 / JavaScript (js) (0) | 2023.02.20 |
프로그래머스 - Level 1. 로또의 최고 순위와 최저 순위 / JavaScript (js) (0) | 2023.02.18 |
프로그래머스 - Level 1. 햄버거 만들기 / JavaScript (js) (0) | 2023.02.17 |
프로그래머스 - Level 2. JadenCase 문자열 만들기 / JavaScript (js) (0) | 2023.02.16 |