키패드 누르기

LEEHAKJIN-VV·2022년 5월 20일
0

프로그래머스

목록 보기
6/37

출처: 프로그래머스 코딩 테스트 연습

문제 설명

스마트폰 전화 키배드의 각 칸에 다음과 숫자들이 적혀 있습니다.

이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.
맨 처음 왼손 엄지손가락은 * 키패드에 오른손 엄지손가락은 # 키패드 위치에서 시작하며, 엄지손가락을 사용하는 규칙은 다음과 같습니다.

  1. 엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.

  2. 왼쪽 열의 3개의 숫자 1, 4, 7을 입력할 때는 왼손 엄지손가락을 사용합니다.

  3. 오른쪽 열의 3개의 숫자 3, 6, 9를 입력할 때는 오른손 엄지손가락을 사용합니다.

  4. 가운데 열의 4개의 숫자 2, 5, 8, 0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.
    4-1. 만약 두 엄지손가락의 거리가 같다면, 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.

순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 하도록 solution 함수를 완성해주세요.

제한 사항

  • numbers 배열의 크기는 1 이상 1,000 이하입니다.
  • numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.
  • hand는 "left" 또는 "right" 입니다.
  • "left"는 왼손잡이, "right"는 오른손잡이를 의미합니다.
  • 왼손 엄지손가락을 사용한 경우는 L, 오른손 엄지손가락을 사용한 경우는 R을 순서대로 이어붙여 문자열 형태로 return 해주세요.

입출력 예

numbershandresult
[1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5]"right""LRLLLRLLRRL"
[7, 0, 8, 2, 8, 3, 1, 5, 7, 6, 2]"left""LRLLRRLLLRR"
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]"right""LLRLLRLLRL"

입출력 예 설명

입출력 예 설명은 문제 원본을 확인한다.

내가 제출한 코드

import Foundation

func solution(_ numbers:[Int], _ hand:String) -> String {
    var result: String = ""
    var handPosition: [Int] = [10, 12] // [left, right]
    let leftHand: [Int] = [1, 4, 7] // 왼쪽 손만 움직임
    let rightHand: [Int] = [3, 6, 9] // 오른쪽 손만 움직임
    let middleHand: [Int] = [2,5,8,0] // 가까운 손이 움직임
    
    let keyPad = makeKeyPad() // KeyPad 만듬
    
    for number in numbers {
        var nextHand = ""
        if leftHand.contains(number) {
            handPosition[0] = number
            nextHand = "L"
        } else if rightHand.contains(number) {
            handPosition[1] = number
            nextHand = "R"
        } else if middleHand.contains(number) { // 왼손과 오른손 중 가까운 손 이동
            nextHand = calDistance(hand, &handPosition, number, keyPad)    
        } else {
            print("invaild input")
        }
        result.append(nextHand)
    }
    return result
}

func makeKeyPad() -> Dictionary<Int,[Int]> {
    var tmpDict: [Int:[Int]] = [:] 
    tmpDict[0] = [3,1] // 숫자 0의 위치 지정
    var index = 1
    
    // for loop를 이용한 키패드 만듬
    for i in 0...3 {
        for j in 0...2 {
            tmpDict[index] = [i,j]
            index += 1
        }
    }
    return tmpDict
}

func calDistance(_ hand: String, _ position: inout [Int], _ nextPosition: Int, _ keyPad: Dictionary<Int,[Int]>) -> String{
    var result: String = "R"
    if let leftPos = keyPad[position[0]], let rightPos = keyPad[position[1]], let nextPos = keyPad[nextPosition] {
        let leftDis = abs(leftPos[0] - nextPos[0]) + abs(leftPos[1] - nextPos[1]) // 왼손과 다음에 누를 숫자와의 거리
        let rightDis = abs(rightPos[0] - nextPos[0]) + abs(rightPos[1] - nextPos[1]) // 오른손과 다음에 누를 숫자와의 거리
        
        // 다음손 결정
        switch leftDis-rightDis {
        case ...(-1):
            position[0] = nextPosition
            result = "L"
        case 1...:
            position[1] = nextPosition
        case 0:
            if hand=="left"{
                position[0] = nextPosition
                result = "L"
            } else {
                position[1] = nextPosition
            }
        default:
            print("error")
        }
    }
    return result
}

코드 설명

풀이는 크게 3가지 과정으로 진행된다.

  1. 스마트폰 전화 키패드 만들기
  2. 순서대로 누를 번호를 담은 배열의 element를 순회
  3. 각 element를 calDistance 함수를 이용하여 어떤 손으로 키패드를 누를지 결정

그러면 각 과정을 세부적으로 살펴본다.

func makeKeyPad() -> Dictionary<Int,[Int]> {
    var tmpDict: [Int:[Int]] = [:] 
    tmpDict[0] = [3,1] // 숫자 0의 위치 지정
    var index = 1
    
    // for loop를 이용한 키패드 만듬
    for i in 0...3 {
        for j in 0...2 {
            tmpDict[index] = [i,j]
            index += 1
        }
    }
    return tmpDict
}
}

우선 스마트폰 키패드를 표현할 자료구조를 딕셔너리로 선택하였다. 딕셔너리의 타입은 [Int:[Int]]이다. key는 키패드에 적힌 숫자를 의미하고, value는 각 숫자의 인덱스(위치)를 의미한다. "*"은 숫자 10, 0은 숫자 11, "#"은 숫자 12라고 가정한다.

for number in numbers {
        var nextHand = ""
        if leftHand.contains(number) {
            handPosition[0] = number
            nextHand = "L"
        } else if rightHand.contains(number) {
            handPosition[1] = number
            nextHand = "R"
        } else if middleHand.contains(number) { // 왼손과 오른손 중 가까운 손 이동
            nextHand = calDistance(hand, &handPosition, number, keyPad)    
        } else {
            print("invaild input")
        }
        result.append(nextHand)
    }

순서대로 누를 배열을 순회한다. 1,4,7인 경우는 왼쪽(leftHand), 3,6,9인 경우는 오른쪽(rightHand) 나머지(middleHand)인 경우는 가까운 거리에 있는 손으로 키패드를 누른다. 이때 가까운 거리에 있는 손을 알아내기 위해 calDistance 함수를 정의하였다. 함수의 반환값은 움직일 손을 반환한다.

func calDistance(_ hand: String, _ position: inout [Int], _ nextPosition: Int, _ keyPad: Dictionary<Int,[Int]>) -> String{
    var result: String = "R"
    if let leftPos = keyPad[position[0]], let rightPos = keyPad[position[1]], let nextPos = keyPad[nextPosition] {
        let leftDis = abs(leftPos[0] - nextPos[0]) + abs(leftPos[1] - nextPos[1]) // 왼손과 다음에 누를 숫자와의 거리
        let rightDis = abs(rightPos[0] - nextPos[0]) + abs(rightPos[1] - nextPos[1]) // 오른손과 다음에 누를 숫자와의 거리
        
        // 다음손 결정
        switch leftDis-rightDis {
        case ...(-1):
            position[0] = nextPosition
            result = "L"
        case 1...:
            position[1] = nextPosition
        case 0:
            if hand=="left"{
                position[0] = nextPosition
                result = "L"
            } else {
                position[1] = nextPosition
            }
        default:
            print("error")
        }
    }
    return result
}

우선 딕셔너리의 서브스크립트를 이용한 값 접근은 옵셔널 바인딩 또는 강제적 언래핑을 해야 한다. 이유는 서브스크립트로 접근하는 key가 존재하지 않을 경우를 대비해 반환 값이 옵셔널 타입이기 때문이다. 이 코드에서는 옵셔널 바인딩을 사용하였다. 현재 손의 위치는 position 파라미터로 나타낸다. 현재 손의 위치와 다음에 누를 키패드와의 거리는 위치의 인덱스를 이용하여 계산하였다.

계산 과정은 다음과 같다.

  1. 현재 왼손, 오른손 위치의 (x,y)인덱스를 얻음(2차원 배열이므로)
  2. 다음에 눌러야 할 숫자의 인덱스를 얻음
  3. 왼손과 다음에 누를 숫자와의 거리, 오른손과 다음에 누를 숫자와의 거리를 계산
  4. 마이너스 연산과 절댓값을 이용함
  5. 가까이 있는 손을 다음에 누른다고 결정, 이때 거리가 같을 시 주로 사용하는 손을 참고하여 결정

결국 순서대로 눌러야 할 번호가 담긴 배열(numbers)을 끝까지 순회하면 움직인 손의 이동경로를 파악할 수 있고 이를 반환하면 문제를 해결한다.

몰랐던 사실 or 기억하면 도움이 될 만한 사실

단방향 범위

단방향 범위 연산자는 시작 혹은 끝만 지정해 사용하는 범위 연산자이다. 범위 연산자는 점(.)을 3개를 이용하여 표현한다.

let range = ...3

이 단방향 연산자를 위 코드에서 switch문에서 사용하였다. 그런데 (...a)인 경우 a를 포함하는 범위인가? a를 포함하지 않는 범위인가? (b...)인 경우는 어떤 범위일까? 아래 예제를 통해 확인할 수 있다.

let rangeA = ...3
rangeA.contains(3) // true

let rangeB = 1...
rangeB.contains(1) // true

위 코드에서 확인할 수 있듯이 단방향 범위 연산자에서 지정한 숫자를 포함하는 것을 확인할 수 있다.

0개의 댓글