Practice for coding test

프로그래머스 - Level 1. 키패드 누르기 / JavaScript (js)

Gray Park 2023. 2. 19. 11:30
728x90
반응형

문제설명

문제분석

이 문제는 모바일 키패드를 누르는 문제입니다. 정확히는 눌러야하는 번호에 따라 가까운 위치의 손을 기록하는 문제입니다. 왼쪽의 1, 4, 7은 왼손으로, 오른쪽의 3, 6, 9는 오른손으로 누르며, 가운데 2, 5, 8, 0은 두 손 중 가까운 손이 누릅니다. 이때 두 손의 거리가 같다면, 오른손잡이인지 왼손잡이인지에 따라 누르는 손이 결정됩니다. 거리는 키패드의 상하좌우를 한칸 이동할 때 1씩 증가하며, 가운데 번호를 누를 때에는 반드시 현재 손가락의 위치부터 눌러야하는 번호까지의 거리를 구해야 합니다. 따라서 현재 손가락의 위치를 기억하고 있어야 합니다. 문제가 조금 복잡한 관계로 풀어서 정리하겠습니다.

  1. 키패드는 가로 3, 세로 4로 정해져 있다.
  2. 키패드의 왼쪽 3개 숫자 1, 4, 7은 항상 왼손으로, 오른쪽 3개 숫자 3, 6, 9는 항상 오른손으로 누른다.
  3. 왼손과 오른손은 각각 *과 # 위치에서 출발한다.
  4. numbers의 요소에 따라 손가락을 움직이고, 키패드 가운데의 2, 5, 8, 0은 거리가 가까운 손으로 누른다.
  5. 이때 오른손과 왼손으로부터 눌러야할 번호와 각각의 거리가 같다면, 오른손잡이라면 오른손으로, 왼손잡이라면 왼손으로 누른다.
  6. 번호를 누르면 항상 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와 같은 배열은 반복문 안에서 매번 새롭게 선언하는 것보다, 한 번 선언과 할당을 해두고 여러번 사용하는 것이 이득이라고 생각했습니다.

물론 코드 관리 면에서도 상단에서 기록해두는 게 용이합니다. 만약 상황이 달라져서 왼손과 오른손으로 누를 수 있는 범위가 달라진다면 언제든지 코드 상단만 수정하여 같은 코드를 사용할 수 있기 때문입니다.

 

728x90
반응형