문제 링크

카카오 코딩테스트: 개인정보 수집 유효기간

문제 분석

숫자를 년,월,일로 바꾸고 [String] 배열을 순회하면서 유효기간을 계산한 뒤, 만료일과 현재 날짜를 비교하면 되는 문제라고 생각했다.

이 때 월, 일이 각각 오버플로우/언더플로우 되는 상황을 주의해야 한다.

비교는 년도 > 월 > 일 순서로 진행했다.


내 코드

import Foundation

func solution(_ today: String, _ terms: [String], _ privacies: [String]) -> [Int] {
    var answer: [Int] = []

    var termDict: [String: Int] = [:]

    for term in terms {
        let parts = term.components(separatedBy: " ")
        let type = parts[0]
        let months = Int(parts[1])!
        termDict[type] = months
    }

    let todayParts = today.components(separatedBy: ".").map { Int($0)! }
    let (todayYear, todayMonth, todayDay) = (todayParts[0], todayParts[1], todayParts[2])

    for (i, privacy) in privacies.enumerated() {
        let parts = privacy.components(separatedBy: " ")
        let dateParts = parts[0].components(separatedBy: ".").map { Int($0)! }
        let (y, m, d) = (dateParts[0], dateParts[1], dateParts[2])
        let termType = parts[1]

        // 유효기간 가져오기
        let validMonths = termDict[termType]!
        // 만료일 계산: 수집일 + 유효기간(달) - 1일
        var expireYear = y
        var expireMonth = m + validMonths
        var expireDay = d - 1

        // 월 오버플로우 처리
        while expireMonth > 12 {
            expireYear += 1
            expireMonth -= 12
        }

        // 일 언더플로우 처리 (day - 1이 0이 되는 경우)
        if expireDay < 1 {
            expireMonth -= 1
            expireDay = 28

            // 월 언더플로우 처리
            if expireMonth < 1 {
                expireYear -= 1
                expireMonth = 12
            }
        }

        // 오늘과 만료일 비교 (오늘이 만료일보다 크면 파기 대상)
        if isExpired(todayYear, todayMonth, todayDay, expireYear, expireMonth, expireDay) {
            answer.append(i + 1)
        }
    }

    return answer
}


// 만료 여부 확인 함수
func isExpired(_ todayYear: Int, _ todayMonth: Int, _ todayDay: Int, 
               _ expireYear: Int, _ expireMonth: Int, _ expireDay: Int) -> Bool {
    // 연도 비교
    if todayYear > expireYear { return true }
    if todayYear < expireYear { return false }
    
    // 월 비교
    if todayMonth > expireMonth { return true }
    if todayMonth < expireMonth { return false }
    
    // 일 비교
    return todayDay > expireDay
}

그러나, 월, 일을 비교하고 오버/언더플로우를 처리하는 과정은 조금 복잡하고 실수할 수 있는 영역이기 때문에, 년도, 월을 모두 일로 변환하여 푸는 것으로 코드를 개선하였다. (이 문제의 조건 중 모든 달은 28일까지 있다는 조건 사용)

또한, 일수로 변환하는 것은 따로 함수로 뺐다.

이렇게 하면 코드의 가독성을 증가시키고, 역할 분리, 재사용 할 수 있게 된다.

import Foundation

func solution(_ today: String, _ terms: [String], _ privacies: [String]) -> [Int] {
    func dateToNumber(_ dateStr: String) -> Int {
        let com = dateStr.components(separatedBy: ".").map { Int($0)! }
        let (year, month, day) = (com[0], com[1], com[2])

        let yearDiff = year - 2000  // 2000년도 부터 시작
        let monthDiff = month - 1   // 0부터 시작
        let dayDiff = day - 1   // 0부터 시작

        // 모든 달이 28일이므로, 1년(12*28), 1달(28)
        return yearDiff * 12 * 28 + monthDiff * 28 + dayDiff
    }

    // 약관별 유효기간을 저장할 딕셔너리
    var termMap: [String: Int] = [:]
    for term in terms {
        let com = term.components(separatedBy: " ")
        let type = com[0]
        let period = Int(com[1])!
        termMap[type] = period
    }

    // 오늘 날짜를 숫자로 변환
    let todayNumber = dateToNumber(today)

    // 파기해야 할 개인정보 번호들을 저장할 배열
    var result: [Int] = []

    // 각 개인정보를 확인
    for (index, privacy) in privacies.enumerated() {
        let com = privacy.components(separatedBy: " ")
        let dateStr = com[0]
        let termType = com[1]
        
        // 수집일을 숫자로 변환
        let collecDate = dateToNumber(dateStr)

        // 유효기간(개월)을 일수로 변환하여 만료일 계산
        let validPeriodInDays = termMap[termType]! * 28
        let expireDate = collecDate + validPeriodInDays

        // 오늘 날짜가 만료일보다 크거나 같으면 파기 대상
        if todayNumber >= expireDate {
            result.append(index + 1)
        }
    }

    return result
}

배운 점

년도, 월, 일을 각각 숫자로 변환하고 각각 비교해야 한다는 생각 때문에 시간이 꽤 걸렸다.

이렇게 ‘일’의 제한이 있는 간단한 문제의 경우, “년도, 월을 모두 일로 변환 시켜 계산” 한다는 아이디어만 있으면, 빠르게 문제를 해결할 수 있다.

추가

사실 이 문제의 경우 Swift에서 제공하는 DateFormatter()를 사용하면 아주 간단하게 풀 수 있다. 만약 DateFormatter() 를 모르면, 위 방법으로 해결해도 무방하다.