(Swift) LeetCode 937. Reorder Data in Log Files

SteadySlower·2023년 12월 5일
0

Coding Test

목록 보기
284/305

문제 링크

문제 풀이 아이디어

String 미리 파싱 해두기

주어진 조건 그대로 정렬하는 함수를 구현하면 된다. String을 그대로 사용하면서 별도의 함수를 구현하는 방법도 있겠으나 그러면 String을 id와 log 내용으로 나누는 작업을 매번 해야한다. Swift는 String을 SubString으로 변경하는 작업의 Cost가 크다. 따라서 Log라는 자료형을 만들어서 처음에 모두 분리하고 시작한다.

마찬가지로 String을 다시 합치는 것도 비용이 크다. 다시 합칠 필요가 없도록 originalString 역시 해당 자료형에 저장해둔다.

Comparable 프로토콜 사용하기

좀 더 Swift 답게 풀기 위해서 별도의 함수가 아닌 Comparable 프로토콜을 활용한다. 정렬하는 함수를 “static func <”에 구현하면 된다. 이 때 주의할 점이 하나 있다.

✋ 입력된 순서대로 정렬하기 위해서는 “return false”

문제의 조건 중에 log의 내용이 숫자인 경우에는 입력된 순서대로 저장 하라는 조건이 있다. 해당 조건을 구현하기 위해서는 “return true”가 아니라 “return false”를 해야 한다. 아래 예시에서 보듯이 Array의 순서대로 lhs, rhs가 아니라 뒤의 것이 lhs, 앞의 것이 rhs가 되기 때문이다. Swift 정렬 알고리즘의 내부는 Timsort로 되어 있는데 그와 연관이 있다고 추측한다.

struct Number: Comparable {
    let num: Int
    
    static func < (lhs: Number, rhs: Number) -> Bool {
        print(lhs.num, rhs.num)
        return false
    }
}

print([1, 2, 3, 4, 5].map { Number(num: $0) }.sorted(by: <).map { $0.num })

/*
 🖨️ 출력 결과
2 1
3 2
4 3
5 4
[1, 2, 3, 4, 5]
 */

문자 로그인지 숫자 로그인지 판단하기

나는 문자 로그인지 숫자 로그인지 판단하기 위해서 Int의 initializer를 사용했다. Int(”문자열”)을 하면 “문자열”이 숫자로만 이루어져 있다면 해당 Int 값을 아니라면 nil을 반환하기 때문이다.

✋ 하지만 여기서 주의할 점이 있다. 문자열을 Int로 바꾸었을 때 너무 큰 숫자여서 Int 자료형이 담을 수 없는 경우에도 nil이 나온다는 것이다. Int 자료형이 담을 수 있는 가장 큰 수는 “9223372036854775807”이다.

문제의 조건을 보면 로그의 길이는 최대 100인데 극단적인 예시로 아래 처럼 9가 100번 반복된 문자열이 나온다면 Int로 변환해도 nil이 나오게 된다.

따라서 나는 log의 첫번째 글자만 따와서 문자 로그인지 숫자 로그인지 판단하는 기준으로 사용했다.

print(Int("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"))

//🖨️ 출력 결과 -> nil

코드

이외 설명은 주석으로 갈음한다.

// 로그 자료형
struct Log: Comparable {
    
    let originalString: String
    let id: String
    let logs: String
    let isLetter: Bool
    
    init(_ s: String) {
        self.originalString = s
        // " "를 기준으로 split
        let array = s.split(separator: " ").map { String($0) }
        self.id = array[0]
        self.logs = array.dropFirst().joined(separator: " ")
        //✋ 그냥 Int로 하면 너무 커서 nil됨
        self.isLetter = Int(String(array[1].first!)) == nil
    }
    
    static func < (lhs: Log, rhs: Log) -> Bool {
        // 둘의 로그 타입이 일치하지 않는 경우
        guard lhs.isLetter == rhs.isLetter else {
            return lhs.isLetter // 문자 로그가 앞에 온다
        }
        
        // 둘 다 숫자 로그인 경우
        guard lhs.isLetter else {
            return false // 입력 순서대로
        }
        
        // 둘 다 문자 로그이고 둘이 로그가 일치하지 않는 경우
        guard lhs.logs == rhs.logs else {
            return lhs.logs < rhs.logs // 로그의 사전순
        }
        
        // 둘 다 문자 로그이고 둘의 로그가 일치하는 경우
        return lhs.id < rhs.id // id의 사전순
    }
    
}

class Solution {
    func reorderLogFiles(_ logs: [String]) -> [String] {
        return logs.map { Log($0) }.sorted(by: <).map { $0.originalString }
    }
}
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글