
개인정보 수집 유효기간 - Swift 문제풀이
문제 링크
카카오 코딩테스트: 개인정보 수집 유효기간
문제 분석
숫자를 년,월,일로 바꾸고 [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() 를 모르면, 위 방법으로 해결해도 무방하다.