리스트의 정렬
프로그래밍 언어에서 리스트 혹은 배열을 정렬하는 알고리즘은 다양하다. 가장 간단한 선택 정렬, 버블 정렬부터 삽입 정렬, 쉘 정렬, 병합 정렬, 퀵 정렬 등의 효율성을 높인 알고리즘들이 존재하고 특별한 형태로 기수 정렬이나 트리 정렬 등의 알고리즘도 존재한다. 하지만 이런 내용들은 알고리즘과 관련된 포스팅할 때 더욱 자세히 하도록 하고, 오늘은 파이썬 내장함수를 이용한 정렬 방법을 정리해보려고 한다.
파이썬에서 리스트를 정렬하는 내장 함수는 대표적으로sort와sorted가 존재한다. 기본적인 함수의 사용 방법과 활용 예시를 살펴보고, 두 함수의 차이를 비교하여 두 함수를 어떻게 하면 더욱 효율적으로 사용할 수 있는지 파악해보자.
sort와 sorted 함수
(1) sort 함수의 기본 형태
sort 함수는 리스트 내부에 있는 요소들을 정렬해주는 함수이다. 함수 내 reverse flag의 default가 False로 되어 있어 기본적으로 오름차순이며, reverse flag를 True로 바꿔줌으로써 내림차순으로 정렬하는 것도 가능하다. 그건 함수의 괄호 안에 reverse = True 를 써주면 된다.
# 가장 기본적인 sort 함수의 활용 예시
lst = [65, 32, 76, 12, 3, 63, 98, 21]
lst.sort()
print(lst) # [3, 12, 21, 32, 63, 65, 76, 98]
# 내림차순으로 정렬하는 sort 함수의 활용 예시
lst = [65, 32, 76, 12, 3, 63, 98, 21]
lst.sort(reverse = True)
print(lst) # [98, 76, 65, 63, 32, 21, 12, 3]
리스트의 요소가 숫자가 아닌 문자열이더라도 정렬이 가능하다. 내림차순으로 정렬하는 것도 물론 가능하다. 이때 중요한 점은 문자열 내의 글자 하나하나의 유니코드를 기준으로 정렬한다는 점인데 아래의 예시를 통해 더 자세하게 살펴보자.
# 한글로만 구성된 문자열일 경우
lst = ['안녕', '하세요', '감사', '해요', '잘', '있어요', '다시', '만나요']
lst.sort()
print(lst) # ['감사', '다시', '만나요', '안녕', '있어요', '잘', '하세요', '해요']
# 영어로만 구성된 문자열일 경우
lst = ['I', 'have', 'been', 'on', 'the', 'low', 'I', 'been', 'taking', 'my', 'time']
lst.sort()
print(lst) # ['I', 'I', 'been', 'been', 'have', 'low', 'my', 'on', 'taking', 'the', 'time']
# 한글로 구성된 문자열과 영어로 구성된 문자열이 혼합될 경우
lst = ['안녕', '세상아', '반갑다', '파이썬', 'Hello', 'World', 'Hi', 'Python']
lst.sort()
print(lst) # ['Hello', 'Hi', 'Python', 'World', '반갑다', '세상아', '안녕', '파이썬']
# 알파벳의 유니코드 vs. 한글의 유니코드
print(ord('H')) # 72
print(ord('반')) # 48152
한글로만 구성된 문자열이나 영어로만 구성된 문자열의 경우 흔히들 알고 있는 사전 순으로 정렬이 되는 것을 알 수 있다. '사전 순'이라는 것의 기준이 되는 것은 요소의 첫 번째 글자이며, 첫 번째 글자가 동일할 경우 그 다음 글자를 기준으로 사전 순으로 정렬된다. 두 번째 글자까지 동일할 경우, 동일하지 않은 n번째 글자까지 비교하게 된다. 위 코드에서 9번 라인을 보면 taking과 the, time을 통해 확인할 수 있다.
하지만 한글로 구성된 문자열과 영어로 구성된 문자열이 혼합될 경우, 사전순으로 정렬하되 알파벳이 먼저 나오는 것을 확인할 수 있다. 이는 영어의 기본이 되는 알파벳의 유니코드가 한글의 기본이 되는 자음과 모음, 혹은 글자들의 유니코드보다 한참 앞에 있기 때문이다. 혹시라도 궁금한 사람들을 위해 하나의 글자에 대한 유니코드 값을 반환해주는 내장함수 ord를 이용하여 비교해놓았다.
위에 언급한 원리에 따라 세계 어느 나라의 언어가 리스트의 요소로 주어져도 이미 정해져있는 유니코드의 값을 기준으로 정렬하게 된다. 당연한 말이지만 내림차순으로 정렬하는 것도 마찬가지의 원리가 적용된다.
(2) sort 함수의 옵션에서 key 함수를 이용한 정렬 방법
만약 특정한 조건에 따라 리스트를 정렬하고 싶다면 어떻게 해야 할까? 그럴 때는 sort 함수의 옵션에서 key 함수를 활용하여 정렬의 특별한 기준을 새롭게 정의할 수 있다.
문자열을 리스트 내 요소들의 사전순이 아닌, 리스트 내 요소들의 길이를 기준으로 정렬하고 싶다면 어떻게 정렬할까? 혹은 리스트 안에 리스트, 혹은 튜플, 문자열과 iterable한 요소들이 존재하고 그 요소들의 특정 인덱스에 위치한 것을 기준으로 정렬하고 싶다면 어떻게 정렬할까? 아니면 딕셔너리의 value를 이용하여 정렬할 수는 있을까? 아래의 코드를 통해 확인해보자.
# 문자열의 길이를 기준으로 정렬하는 방법
lst = ['I', 'have', 'been', 'on', 'the', 'low', 'I', 'been', 'taking', 'my', 'time']
lst.sort(key = lambda x : len(x))
print(lst) # ['I', 'I', 'on', 'my', 'the', 'low', 'have', 'been', 'been', 'time', 'taking']
# iterable한 객체 내 특정 인덱스에 위치한 요소를 기준으로 정렬하는 방법
lst = [(10, 5), (12, -1), (42, 16), (2, 13), (9, 15)]
lst.sort(key = lambda x : x[1], reverse = True)
print(lst) # [(42, 16), (9, 15), (2, 13), (10, 5), (12, -1)]
# 딕셔너리 내 특정 key의 value를 기준으로 정렬하는 방법
lst = [{'name':'단팥빵', 'quantity':10},
{'name':'호빵', 'quantity':5},
{'name':'꽈배기', 'quantity':12},
{'name':'피자빵', 'quantity':6}]
lst.sort(key = lambda x : x['quantity'])
print(lst)
# [{'name': '호빵', 'quantity': 5},
# {'name': '피자빵', 'quantity': 6},
# {'name': '단팥빵', 'quantity': 10},
# {'name': '꽈배기', 'quantity': 12}]
핵심은 sort 함수의 괄호 안에 key = lambda 를 활용하는 것이다. lambda에 대해서는 나중에 더 자세하게 포스팅을 할 예정인데, 간단히 말해 sort에서의 lambda는 아래와 같은 형식으로 쓰이는 것이 일반적이다.
list.sort(key = lambda x : ____________)
key는 정렬 시 비교할 값을 return하는 함수이며, x는 리스트 내 원소(요소)를 의미한다. 콜론 뒤에 밑줄에는 key 함수를 활용하여 반환하고자 하는 값을 넣으면 되는데 이것이 곧 정렬의 기준이 될 것이다. 위 코드에 활용한 예시들에서 사용한 정렬의 기준을 순서대로 말하자면 (1) 문자열의 길이, (2) 튜플 내 두 번째 원소의 값, (3) 딕셔너리 내 'quantity'라는 key에 담겨있는 value 값이다. 이런 식으로 본인이 원하는 특정 기준이 맞춰 정렬할 수도 있다.
(3) sorted 함수
그렇다면 sort 함수가 버젓이 존재하는데 sorted 함수는 왜 존재하는 것일까? 아래 코드를 통해 두 함수의 차이점을 확인해보자.
# 새로운 객체에 lst.sort()를 할당한다면?
lst = [3, 7, 5, 9, 1]
new_lst = lst.sort()
print(new_lst) # None
print(type(new_lst)) # <class 'NoneType'>
# 새로운 객체에 sorted(lst)를 할당한다면?
lst = [3, 7, 5, 9, 1]
new_lst = sorted(lst)
print(new_lst) # [1, 3, 5, 7, 9]
print(type(new_lst)) # <class 'list'>
설명 없이도 위 코드를 통해 눈치챘다면 다 이해한 것이다.sort 함수는 그저 특정 리스트를 정렬해주는 기능을 해주는 함수일 뿐, 반환값이 없는 함수이다. 그래서 sort 함수를 새로운 객체에 할당하게 될 경우 None이 할당되며, 그 객체의 type은 NoneType이 된다.
반면 sorted 함수는 특정 리스트를 정렬한 후 정렬된 리스트를 반환해주는 함수로, sort 함수와 달리 반환값이 존재하는 함수이다. 그래서 sorted 함수를 새로운 객체에 할당하게 될 경우 정렬된 리스트가 새로운 객체에 할당되며, 당연히 그 객체의 type은 list가 된다.
이게 그렇게 큰 의미를 가질까? 난 그렇다고 생각한다. 상당히 편리하게 이용할 수 있는데, 기존의 리스트를 변경시키지 않고 정렬된 리스트를 새롭게 얻고 싶다면 sorted 함수를 쓰면 된다. 혹은 코드 내에 한 줄이라도 덜 쓰고 for문을 돌리고 싶다면 for i in sorted(lst) 와 같은 식으로 활용할 수도 있다. 또한, 정렬을 하는 동시에 deep copy도 이루어지기 때문에 원본 데이터에 영향을 주지 않는다는 장점도 존재한다.
sort 함수와 마찬가지로 sorted 함수도 reverse flag를 활용하여 내림차순으로 정렬할 수도 있고, key 함수 또한 동일하게 사용할 수 있다.
# sorted 함수에서 reverse flag를 True로 변경하는 예시
lst = [3, 7, 5, 9, 1]
new_lst = sorted(lst, reverse = True)
print(new_lst) # [9, 7, 5, 3, 1]
# sorted 함수에서 key함수를 활용하는 예시
lst = [(10, 5), (12, -1), (42, 16), (2, 13), (9, 15)]
new_lst = sorted(lst, key = lambda x : x[1])
print(new_lst) # [(12, -1), (10, 5), (2, 13), (9, 15), (42, 16)]
# sorted 함수에서 key함수와 reverse flag를 모두 활용하는 예시
lst = [(10, 5), (12, -1), (42, 16), (2, 13), (9, 15)]
new_lst = sorted(lst, key = lambda x : x[1], reverse = True)
print(new_lst) # [(42, 16), (9, 15), (2, 13), (10, 5), (12, -1)]
'Dev > Python' 카테고리의 다른 글
[파이썬/Python] 리스트(list)의 메소드 시간 복잡도(Big-O) 정리 (0) | 2020.04.20 |
---|---|
[파이썬/Python] for - else / while - else 활용 (0) | 2020.03.12 |
[파이썬/Python] 순열과 조합 (Permutation and Combination) (0) | 2020.03.10 |
[파이썬/Python] 재귀함수(Recursive function)와 메모이제이션(Memoization) (2) | 2020.03.02 |
[파이썬/Python] 리스트 안에서 반복문, Comprehension (0) | 2020.02.27 |