키패드 누르기

Sheryl Yun·2023년 9월 3일
0

처음 풀이

  • 전화번호 키패드의 각 숫자의 위치를 어떻게 인식시킬까 하다가 좌표를 떠올렸다.
  • 키패드 왼쪽 숫자는 무조건 L, 오른쪽 숫자는 무조건 R이다.
  • 가운데 키패드일 경우(2, 5, 8, 0번)는 절대 값 거리를 구해서 더 가까운 쪽을 반영했다.
function solution(numbers, hand) {
    let answer = '';
    let leftCoord = [3, 0];
    let rightCoord = [3, 2];
    
    // 전화번호에서 각 숫자의 위치를 좌표로 나타냄 (고정된 상수이므로 const로 선언)
    const one = [0, 0], two = [0, 1], three = [0, 2], four = [1, 0], five = [1, 1]
    const six = [1, 2], seven = [2, 0], eight = [2, 1], nine = [2, 2], zero = [3, 1];
    
    for (let number of numbers) {
        // 1, 4, 7 -> 무조건 L
        if (number === 1 || number === 4 || number === 7) {
            answer += 'L';
            leftCoord = number === 1 ? one : number === 4 ? four : seven;
        }
        // 3, 6, 9 -> 무조건 R
        else if (number === 3 || number === 6 || number === 9) {
            answer += 'R';
            rightCoord = number === 3 ? three : number === 6 ? six : nine;
        }
        // 2, 5, 8, 0 -> 거리를 구해서 현재 위치에서 더 가까운 손 / 거리가 같으면 hand로 결정
        else { 
            // 각 엄지와 target 간의 거리
            let leftDistance = 0;
            let rightDistance = 0;
            
            // 2, 5, 8, 0 각 경우 중 현재 왼/오 좌표와 번호 좌표와의 차이의 절대 값을 구해서 둘을 합쳐서 거리 구하기 
            if (number === 2) {
                leftDistance = Math.abs(leftCoord[0] - two[0]) + Math.abs(leftCoord[1] - two[1]);
                rightDistance = Math.abs(rightCoord[0] - two[0]) + Math.abs(rightCoord[1] - two[1]);
            }
            else if (number === 5) {
                leftDistance = Math.abs(leftCoord[0] - five[0]) + Math.abs(leftCoord[1] - five[1]);
                rightDistance = Math.abs(rightCoord[0] - five[0]) + Math.abs(rightCoord[1] - five[1]);
            }
            else if (number === 8) {
                leftDistance = Math.abs(leftCoord[0] - eight[0]) + Math.abs(leftCoord[1] - eight[1]);
                rightDistance = Math.abs(rightCoord[0] - eight[0]) + Math.abs(rightCoord[1] - eight[1]);
            } else {
                leftDistance = Math.abs(leftCoord[0] - zero[0]) + Math.abs(leftCoord[1] - zero[1]);
                rightDistance = Math.abs(rightCoord[0] - zero[0]) + Math.abs(rightCoord[1] - zero[1]);
            }
            
            // 각 거리 구하기가 끝나면 거리가 같은지 여부를 판별
            // 같으면 hand를 따르고 다르면 더 가까운 쪽을 반영
            if (leftDistance === rightDistance) {
                if (hand === "left") {
                    answer += 'L';
                    leftCoord = number === 2 ? two : number === 5 ? five : number === 8 ? eight : zero;
                } else {
                    answer += 'R';
                    rightCoord =  number === 2 ? two : number === 5 ? five : number === 8 ? eight : zero;
                }
            } else {
                if (leftDistance < rightDistance) {
                    answer += 'L';
                    leftCoord = number === 2 ? two : number === 5 ? five : number === 8 ? eight : zero;
                } else {
                    answer += 'R';
                    rightCoord =  number === 2 ? two : number === 5 ? five : number === 8 ? eight : zero;
                }
            }
        }
    }
    
    return answer;
}
  • 처음에 풀 때 문제 테스트 케이스 1번에서 한 글자가 계속 기대값과 다르게 나와서 하나하나 콘솔로 찍어보았다.
  • 특정 순간부터 좌표가 갱신되고 있지 않았는데, 마지막에 거리가 같은지 여부를 판별하는 if문에서 answer 값만 바꿔주고 leftCoord 또는 rightCoord 값을 업데이트하지 않은 것이 문제였다.
  • 문제의 테스트 케이스를 모두 통과하고 나서 중복되는 로직을 제거하기 위해 다음과 같이 리팩토링했다.

리팩토링

  • 고려한 사항
    • 선언되는 변수 갯수 줄이기
    • 단일 책임 원칙 (SRP)
      • 하나의 함수에서 한 가지 일만 하게 한다
    • 매번 선언될 필요가 없는 함수와 변수는 solution 밖으로 뺌
  • 바꾼 코드
// keypad 숫자 값을 객체로 변환 (고정 값이므로 const로 선언)
// * const one = ... 식으로 여러 개 선언했던 변수들을 객체 1개로 줄임
// * keypad의 좌표 값을 가져올 때 `객체[key]` 형태로 간편하게 가져올 수 있게 됨 (number === 1 등의 로직 모두 삭제)
const keyCoord = {
    1: [0, 0],
    2: [0, 1],
    3: [0, 2],
    4: [1, 0],
    5: [1, 1],
    6: [1, 2],
    7: [2, 0],
    8: [2, 1],
    9: [2, 2],
    0: [3, 1]
}

// 왼손/오른손 좌표 (변경 가능하므로 let으로 선언)
let leftCoord = [3, 0]; // 초기값: '*'
let rightCoord = [3, 2]; // 초기값: '#'

// 절대값 거리를 구하는 로직을 getDistance 함수로 따로 분리
// * solution 함수 안에서는 거리를 구하는 과정을 알 필요가 없기 때문
function getDistance(number) {
    const numberCoord = keyCoord[number]; // return 문에서 반복되므로 변수로 뺌
    
    return {
        leftDistance: Math.abs(leftCoord[0] - numberCoord[0]) + Math.abs(leftCoord[1] - numberCoord[1]),
        rightDistance: Math.abs(rightCoord[0] - numberCoord[0]) + Math.abs(rightCoord[1] - numberCoord[1])
    }
}

function solution(numbers, hand) {
    let answer = '';
    
    for (let number of numbers) {
    	// number가 1, 4, 7인지 3, 6, 9인지 구분하는 if 조건문 수정
        // * 규칙을 찾아내어 나머지 연산자(%)로 단순화
        // * 마지막 else문에만 0을 적용하기 위해 1번째와 2번째 조건문에 `number &&` 추가
        if (number && number % 3 === 1) { // 1, 4, 7인 경우
            answer += 'L';
            leftCoord = keyCoord[number];
        }
        else if (number && number % 3 === 0) { // 3, 6, 9인 경우
            answer += 'R';
            rightCoord = keyCoord[number];
        }
        else {
            // getDistance 함수에서 leftDistance, rightDistance 반환
            // 비구조화 할당으로 꺼내서 사용
            const { leftDistance, rightDistance } = getDistance(number);
            
            // && 연산자를 사용하여 중첩 if문을 줄이고 
            // 중복되는 로직은 || 연산자로 합침
            if (leftDistance === rightDistance && hand === "left" || leftDistance < rightDistance) {
                answer += 'L';
                leftCoord = keyCoord[number];
            } else {
                answer += 'R';
                rightCoord = keyCoord[number];
            }
        }
    }
    
    return answer;
}
  • else문을 최대한 빼려고 해봤는데 로직이 'A가 아니면 B'라는 구조여서 그런지 에러가 나서 그대로 두었다.
  • 처음 코드보다 확실히 가독성이 높아지고 간결해진 것 같다.
    (문제 푸는 데 2시간 리팩토링하는 데 1시간)
    리팩토링이 재밌었던 문제였다.
profile
영어강사, 프론트엔드 개발자를 거쳐 데이터 분석가를 준비하고 있습니다 ─ 데이터분석 블로그: https://cherylog.tistory.com/

0개의 댓글