본문 바로가기

알고리즘/LeetCode

Reorder Data in Log Files (Sort)

LeetCode의 Reorder Data in Log Files 문제다.

 

Reorder Data in Log Files - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

주어진 문자열을 특정 조건에 따라 정렬시키는 문제인데 지엽적인 조건이 많아서 그런지 너무 쉬워서 그런 건지 모르겠지만 평가가 별로 좋지 않다(비추가 추천의 3배...). 그래도 파이썬 활용에 익숙해지고 정렬 메서드를 연습하는 김에 풀어보았다.

 

문제의 조건은 다음과 같다.

  • 문제에서는 로그(문자열)가 담긴 리스트가 주어진다.

  • 각 로그는 공백(" ")을 기준으로 나눠진 단어들로 이루어져 있다.

  • 각 로그의 첫 번째 단어는 해당 로그의 식별자에 해당한다.

  • 각 로그의 본문(두 번째 단어 이후)에는 영소문자만 들어있거나 숫자만 들어있다. 이를 각각 문자 로그, 숫자 로그라고 칭한다.

  • 문자 로그는 숫자 로그보다 먼저 정렬(앞에 위치)되어야 한다.

  • 문자 로그는 로그의 본문으로 정렬하며 본문이 동일하다면 식별자로 정렬한다.

  • 숫자 로그는 정렬되지 않고 초기 상태를 유지한다.

뭔가 이것저것 많기 때문에 좀 까다로울 수 있다. 하지만 파이썬의 문자열 메서드와 정렬 메서드를 이용하여 어렵지 않게 풀 수 있었는데 우선 첫 번째로 해야 할 것은 각 로그가 어떤 로그인지 파악하는 것이다.

 

문제에서는 첫 번째 단어는 식별자고 이후 단어들이 영소문자로만 구성되어 있는지, 숫자로만 구성되어 있는지에 따라 로그의 종류를 구분한다고 했다. 그리고 문자 로그들은 무조건 숫자 로그들보다 먼저 정렬되어야 하기 때문에 문자 로그들과 숫자 로그들을 분리하여 나중에 문자 로그들의 정렬이 끝나면 그 뒤에 숫자 로그들을 그대로 붙여서 반환해주는 방법을 생각해볼 수 있을 것이다.

 

로그의 본문에는 오직 영소문자 혹은 오직 숫자만 포함되어 있기 때문에 단어를 하나만 확인해도 해당 로그의 종류를 구분할 수 있다. 이를 위해서는 isdigit() 메서드를 활용할 수 있다.

>>> "123456".isdigit()
True
>>> "abcd56".isdigit()
False

이 메서드는 해당 문자열이 오직 숫자로만 이루어져 있을 때 True를 반환한다. 그러므로 만약 로그의 두 번째 단어(본문의 첫 번째 단어)의 isdigit() 메서드가 참일 경우 해당 로그는 숫자 로그라고, 그렇지 않을 경우 문자 로그라고 구분할 수 있다.

 

이렇게 구분한 후에는 어떻게 할 수 있을까? 숫자 로그들은 정렬을 요구하지 않기 때문에 그냥 문자 로그들 뒤에 삽입하면 된다. 하지만 문자 로그들은 로그 본문으로 정렬돼야 하는데 이때는 리스트의 sort() 메서드를 사용할 수 있다.

>>> l = [1,3,2,4,5]
>>> l.sort()
>>> l
[1, 2, 3, 4, 5]

이는 정수뿐 아니라 문자열도 알파벳 순(첫 글자부터)으로 정렬할 수 있다.

>>> l = ["ab", "a", "b", "abc", "cd"]
>>> l.sort()
>>> l
['a', 'ab', 'abc', 'b', 'cd']

근데 문제에서 주어지는 로그 파일은 식별자와 본체가 같이 붙어있기 때문에 식별자를 분리하고 본체만 가지고 비교하여 정렬할 필요가 있다. 이를 위해서는 리스트의 sort 메서드의 key 파라미터에 람다식을 넘겨서 세부 조건을 지정해줄 수 있다.

>>> l = ["3 a", "4 b", "1 c", "2 b"]
>>> l.sort(key=lambda string: string.split(" ")[1])
>>> l
['3 a', '4 b', '2 b', '1 c']

위의 람다식에서는 문자열을 공백(" ")으로 나눈 리스트의 두 번째 항목을 얻고 있다. 이 람다식을 리스트의 모든 요소에 적용하면 ['a', 'b', 'c', 'b']라는 리스트를 얻을 수 있을 것이다. 이 리스트를 기준으로 원본 리스트를 정렬하면 일단 람다식으로 얻은 리스트는 ['a', 'b', 'b', 'c']가 된다. 이를 원본 리스트의 요소들과 매칭 해서 정렬하면 ['3 a', '4 b', '2 b', '1 c']가 되는 것이다.

>>> l = ["3 a", "4 b", "1 c", "2 b"]
>>> l.sort(key=lambda string: (string.split(" ")[1], string.split(" ")[0]))
>>> l
['3 a', '2 b', '4 b', '1 c']

그런데 여기서 '4 b', '2 b'도 '2 b', '4 b'처럼 한번 더 정렬하고 싶다면 어떨까? 이는 위처럼 sort 메서드의 key 람다식에서 여러 개의 함수가 들어간 튜플을 반환하면 된다. 이 튜플은 리스트 정렬에서 사용되는 키를 순서대로 담고 있는데 첫 번째 키(string.split(" ")[1])로 정렬한 결과 'b', 'b'(원본 리스트에서는 '4 b', '2 b')가 중복되기 때문에 이를 두 번째 키(string.split(" ")[0])로 정렬하여 '4', '2'를 '2', '4'(원본 리스트에서는 '2 b', '4 b')로 정렬한 것이다.

 

마침 문제에서도 로그의 본문이 동일할 경우 식별자로 비교하라고 하고 있기 때문에 정렬에서 여러 개의 키를 활용할 수 있을 것이다. 이를 기반으로 작성한 코드는 다음과 같다.

class Solution:
    def reorderLogFiles(self, logs: List[str]) -> List[str]:
        letter_logs = []
        digit_logs = []
        
        for log in logs:
            if log.split(" ")[1].isdigit():
                digit_logs.append(log)
            else:
                letter_logs.append(log)
            
        letter_logs.sort(key=lambda log: (log.split(" ")[1:], log.split(" ")[0]))
        
        return letter_logs + digit_logs

문자 로그, 숫자 로그를 따로 리스트에 저장하고 있으며 이때 로그는 여러 개의 단어로 이루어졌기 때문에 [1:]처럼 두 번째 단어 이후로 모든 단어를 리스트로 가져오는 함수를 키로 사용하고 있다. 슬라이싱으로 가져오면 리스트 자료형에 담기기 때문에 어떻게 비교될지 헷갈릴 수도 있지만 예상하는 대로 파이썬 내부적으로 잘 비교되는 것 같다. 그리고 중복되는 로그는 식별자로 정렬하라는 조건에 따라 log.split(" ")[0] 키를 추가적으로 전달한 것을 볼 수 있다.

'알고리즘 > LeetCode' 카테고리의 다른 글

Most Common Word  (0) 2021.02.17
String to Integer (atoi)  (0) 2021.02.16
Binary Tree Level Order Traversal (Queue)  (0) 2021.02.16
Binary Tree Postorder Traversal (Stack)  (0) 2021.02.16
Binary Tree Preorder Traversal (Stack)  (0) 2021.02.16