주차요금 계산

LEEHAKJIN-VV·2022년 6월 17일
0

프로그래머스

목록 보기
27/37

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

문제 설명

자세한 문제 설명은 출처 링크 참조

제한 시간 안내

  • 정확성 테스트 10초

입출력 예

snresult
[180, 5000, 10, 600]["05:34 5961 IN", "06:00 0000 IN", "06:34 0000 OUT", "07:59 5961 OUT", "07:59 0148 IN", "18:59 0000 IN", "19:09 0148 OUT", "22:59 5961 IN", "23:00 5961 OUT"][14600, 34400, 5000]
[120, 0, 60, 591]["16:00 3961 IN","16:00 0202 IN","18:00 3961 OUT","18:00 0202 OUT","23:58 3961 IN"][0, 591]
[1, 461, 1, 10]["00:00 1234 IN"][14841]

입출력 예 설명

자세한 입출력 예 설명은 출처 링크 참조

내가 제출한 코드


import Foundation

func solution(_ fees:[Int], _ records:[String]) -> [Int] {
    var answer = [Int]() // 결과
    var notExistNumber = Set<String>() // 입차 출차 관리
    var carTime: [String:Int] = [:] // [차 번호: 누적 주차시간]
    let CHARGE = ["IN":-1, "OUT":1] 
    let FINAL_TIME = "23:59" // 마지막 출차시간
    
    for record in records { // 주차 기록 읽음
        let info = record.split(separator: " ").map{String($0)} // [05:34,5961,IN]
        let currentTime: Int = changeIntTime(info[0]) // 현재 시간 분으로 계산
        checkInOut(&notExistNumber, info[1], info[2]) // 입차 출차 관리
        
        if let carNumber = carTime[info[1]] { // 입차나 출차의 기록이 있는 경우
            carTime[info[1]] = carNumber + currentTime * CHARGE[info[2]]!
        } else { // 처음 입차하는 경우
            carTime[info[1]] = currentTime * CHARGE[info[2]]!
        }
    }
    // 아직 출차하지 않은 차량 23:59 출차로 계산
    for number in notExistNumber {
        if let time = carTime[number] {
            carTime[number] = time + changeIntTime(FINAL_TIME)
        }
    }
    
    let sortedNumber = carTime.sorted {$0 < $1} // 결과는 차량번호의 작은순 이므로 정렬
    for (_, v) in sortedNumber { // 각 차량마다 요금표(fees)를 기준으로 요금 계산
        answer.append(calParkingFee(fees,v))
    }
    return answer
}

// 문자열 시간을 분으로 계산 -> 05:34 -> 334
func changeIntTime(_ time: String) -> Int {
    let timeArray = time.split(separator: ":")
    return Int(timeArray[0])! * 60 + Int(timeArray[1])!
}

// 입차 출차 관리
func checkInOut(_ visited: inout Set<String>, _ number: String, _ behavior: String) {
    if behavior == "IN" { // 입차인 경우 집합에 추가
        visited.insert(number)
    } else { // 출차인 경우 집합에서 제거
        visited.remove(number)
    }
}

// 주차요금 계산
// fees: [기본 시간, 기본 요금, 단위 시간, 단위 요금]
func calParkingFee(_ fees: [Int], _ time: Int) -> Int{
    if time <= fees[0] { // 기본 시간 이하인 경우
        return Int(fees[1])
    }
    return fees[1] + Int(ceil(Double(time-fees[0]) / Double(fees[2])) * Double(fees[3])) // 주차시간 계산
}

결과

코드 설명

이번 문제는 특정한 알고리즘을 사용하지 않고 설명대로 구현하면 해결할 수 있다. 그리고 문제를 보면 표와 글이 섞여 다른 문제보다 길다. 이런 문제를 접할 때는 특히 조건들을 잘 살펴야 한다. 문제에서 주의할 제한사항과 조건들 중에서 몇 가지를 나열하면 다음과 같다.

  1. fees 배열의 모든 원소는 양수이다.
  2. records의 배열의 형식은 고정되어 있다. "시각, 차량번호, 내역"
  3. 차량번호는 숫자로 구성된 4자리의 문자열이다.
  4. records는 하루가 기준이다. 즉 오늘 입차 하여 다음날에 출고하는 경우는 없다.
  5. 차량이 입차된 기록은 있으나 출차된 기록이 없는 경우 "23:59"에 출차된 것으로 간주한다.
    등등..

이 문제에서 고민해야 할 점은 누적 주차 시간이다. 이를 계산하는 방법은 다양할 것이다. 그러나 이번 글에서는 다음 아이디어를 사용한다.

입차 => -시간(분) 출차 => +시간(분)
예로 설명하면, "0000" 차량이 "06:00"시각에 입차하고 "06:34" 시각에 출차한 경우 누적 주차 시간은 -(6*60) + (6*60+34) => -360 + 394 => 34가 된다. 해당 방법으로 모든 차량의 누적 주차시간을 구하고, 요금 표를 이용한 문제를 해결할 수 있다.

그러면 코드를 살펴본다.

풀이에 사용되는 변수이다.

var answer = [Int]() // 결과
var notExistNumber = Set<String>() // 입차 출차 관리
var carTime: [String:Int] = [:] // [차 번호: 누적 주차시간]
let CHARGE = ["IN":-1, "OUT":1] 
let FINAL_TIME = "23:59" // 마지막 출차 시간

다른 변수는 주석을 참고하고, CHARGE 프로퍼티는 누적 주차 시간을 계산하기 위해 사용한 딕셔너리이다.

다음으로 주차 기록 records를 읽는 코드를 확인하자.

for record in records { // 주차 기록 읽음
        let info = record.split(separator: " ").map{String($0)} // [05:34,5961,IN]
        let currentTime: Int = changeIntTime(info[0]) // 현재 시간 분으로 계산
        checkInOut(&notExistNumber, info[1], info[2]) // 입차 출차 관리
        
        if let carNumber = carTime[info[1]] { // 입차나 출차의 기록이 있는 경우
            carTime[info[1]] = carNumber + currentTime * CHARGE[info[2]]!
        } else { // 처음 입차하는 경우
            carTime[info[1]] = currentTime * CHARGE[info[2]]!
        }
    }

records 배열은 [String] 타입이다. 그러므로 각 element를 공백으로 split 하고 사용한다. 이때 split(separator:) 메소드가 반환하는 타입은 Array<Substring>이므로 String타입으로 변환 한다.

checkInOut 함수는 입차와 출차의 목록(입차는 했는데 출차 하지 않은 차를 찾아내는 집합)을 관리한다. 이 함수는 아래에서 설명한다.

마지막 조건문은 차량의 누적 주차 시간을 계산한다. 누적 주차 시간은 앞에서 설명한 대로 계산하니 쉽게 이해할 수 있을 것이다.

다음은 checkInOut 함수를 살펴본다.

// 입차 출차 관리
func checkInOut(_ visited: inout Set<String>, _ number: String, _ behavior: String) {
    if behavior == "IN" { // 입차인 경우 집합에 추가
        visited.insert(number)
    } else { // 출차인 경우 집합에서 제거
        visited.remove(number)
    }
}

문제에서 보면 입차는 하였는데 출차를 하지 않은 차량이 있다. 이를 확인하기 위해 집합을 사용한다. 로직은 간단하다. 입차한 경우 집합에 추가하고, 입차 하지 않은 경우 집합에서 제거한다. 이를 반복하다 보면 결국 입차하였는 데 출차하지 않은 차량만 집합에 남게 될것이다. 그리고 주차장에 없는 차량이 출차되는 경우와 같은 잘못된 입력은 주어지지 않는다고 문제에서 명시하므로, 집합에서 없는 원소를 제거하는 오류는 발생하지 않는다고 확신할 수 있다.

다음은 위의 함수를 이용해 얻은 아직 출차하지 않은 차량의 시각을 마지막 시각으로 간주하여 주차 시간을 계산하는 코드이다.

// 아직 출차하지 않은 차량 23:59 출차로 계산
    for number in notExistNumber {
        if let time = carTime[number] {
            carTime[number] = time + changeIntTime(FINAL_TIME)
        }
    }

출차하지 않은 차량의 출차 시간을 "23:59"으로 간주하고 계산한다.

다음은 주차 요금을 계산하는 코드다.

let sortedNumber = carTime.sorted {$0 < $1} // 결과는 차량번호의 작은순 이므로 정렬
    for (_, v) in sortedNumber { // 각 차량마다 요금표(fees)를 기준으로 요금 계산
        answer.append(calParkingFee(fees.map{Double($0)},Double(v)))
    }
    return answer

우선 결과로 차량번호가 작은 자동차부터 요금을 정산해야 하므로 딕셔너리를 정렬한다. 그리고 정렬한 딕셔너리의 각 element들을 calParkingFee 함수에 할당한다. 이 함수는 요금 표와 주차시간을 이용하여 누적 주차시간을 계산한다.

마지막으로 calParkingFee함수를 살펴보자.

func calParkingFee(_ fees: [Int], _ time: Int) -> Int{
    if time <= fees[0] { // 기본 시간 이하인 경우
        return Int(fees[1])
    }
    return fees[1] + Int(ceil(Double(time-fees[0]) / Double(fees[2])) * Double(fees[3])) // 주차시간 계산
}

계산식은 문제의 설명한 대로 계산한다. 여기서 주의해야 할 점은 계산에 사용되는 값들의 타입이다. 올림을 위해 요금 표의 값들을 Double로 변환한다.

NOTE
코드 설명에서 주어진 시각 "06:34"으로 변환하는 함수changeIntTime는 따로 설명하지 않는다.

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

Swift의 올림, 내림, 반올림

swift에서도 올림, 내림, 반올림 함수를 지원한다.

ceil(2.3)
// Prints 3

floor(2.3)
// Prints 2

round(2.6)
// Prints 3

단 여기서 각 함수에 사용되는 값들은 Double 타입이나 Float 타입이어야 한다. 만약 Double타입으로 호출했다면 반환값도 Double타입이다.

0개의 댓글