🔍 Sorting HOW TO
단어 정렬 문제(백준의 1181)를 풀다가 확실히 정렬에 대해 정리할 필요가 있겠다는 생각이 들어 작성해 본다.
공식 문서를 참고하였고, sort와 sorted 그리고 reverse와 key에 대해서 정리해 보았다.
추가로 파이썬에서 액세스 함수를 더 쉽고 빠르게 만들 수 있도록 제공하는 operator 모듈 함수는 정리하지 않았지만, 알아두면 꽤 쏠쏠할 것 같다. 참고
🗃️ sort() VS sorted()
파이썬에는 리스트를 정렬하는 내장 함수 두 가지가 있음
바로 sort()와 sorted()이며, 둘은 크게 두 가지의 차이점이 있음
1️⃣ 첫 번째 차이점
◻ sort() 함수는 리스트를 제자리에서(in-place) 수정함
◻ sorted() 함수는 새로운 정렬된 리스트를 만들어줌
제자리에서 수정한다는 말은 쉽게 말해 원본 리스트 자체를 수정하지만, 그 결과를 반환해주진 않는다(None)는 뜻임
sort() 함수는 원본 리스트가 변경되어버리지만 sorted() 함수의 경우 원본에는 영향 없이, 새로운 정렬된 리스트를 반환해줌
a = [5, 2, 3, 1, 4]
print(a.sort()) # None 이 출력됨 => 결과를 반환해주지는 않음
print(a) # [1, 2, 3, 4, 5] 가 출력됨 => 원본 리스트가 수정됨
b = [5, 2, 3, 1, 4]
new_b = sorted(b)
print(sorted(b)) # [1, 2, 3, 4, 5] 가 출력됨
print(new_b) # [1, 2, 3, 4, 5] 가 출력됨
print(b) # [5, 2, 3, 1, 4] 가 출력됨 => 원본 리스트는 영향 X, 새로운 정렬된 리스트가 만들어지는 것
📍 추가 정보 list.sort()가 정렬된 리스트를 반환하지 않는 이유
sort() 함수가 정렬된 리스트를 반환하지 않는 이유 역시 공식 문서에 나와있음
간단히 말해 성능 이슈 때문 (단지 정렬하기 위해 리스트를 복사하는 것은 비효율적!)
새 리스트를 반환하려면 sorted() 함수를 사용하면 됨!
2️⃣ 두 번째 차이점
◻ sort() 함수는 리스트에만 사용할 수 있음 (리스트 -> 정렬된 리스트)
◻ sorted() 함수는 Iterable 자료형(타입)에 사용할 수 있으며, 정렬된 리스트를 반환함 (Iterable -> 정렬된 리스트)
(Iterable: 반복 가능한 자료형, 자세한 내용(Iterable과 Iterator)은 추후 정리 예정)
# Iterable 자료형 중 딕셔너리를 사용한 예제
# 딕셔너리를 정렬한 후 리스트로 반환
a = {3: 'C', 2: 'B', 1: 'D', 4: 'E', 5: 'A'}
new1_a = sorted(a)
# 딕셔너리의 key 값으로 정렬하여 key 리스트 반환
print(new1_a) # [1, 2, 3, 4, 5]
↗️↘️ 오름차순과 내림차순, reverse
sort()와 sorted() 두 함수 모두 reverse 매개 변수를 갖고 있음
reverse는 Boolean 값(True, False)을 가지며 기본값은 False임(오름차순)
reverse=True를 입력해주면 내림차순으로 정렬시킬 수 있음!
a = [3, 2, 1, 4, 5]
ascending = sorted(a, reverse=False) # 오름차순, reverse 생략 가능
descending = sorted(a, reverse=True) # 내림차순
print(ascending) # [1, 2, 3, 4, 5]
print(descending) # [5, 4, 3, 2, 1]
a.sort(reverse=True) # 내림차순
print(a) # [5, 4, 3, 2, 1]
🔑 정렬 조건, key
sort()와 sorted() 두 함수 모두 key 매개 변수를 가질 수 있으며 key의 값은 단일 인자를 취하고 정렬 목적으로 사용할 키를 반환하는 함수여야 함!!
보통 람다(lambda, 익명 함수)를 사용하여 key의 값으로 함수를 넘겨줌
# 딕셔너리를 정렬 조건에 따라 정렬한 후 리스트로 반환
a = {3: 'C', 2: 'B', 1: 'D', 4: 'E', 5: 'A'}
new1_a = sorted(a)
new2_a = sorted(a, key=lambda x: a[x])
key_a = sorted(a.items(), key=lambda x: x[0]) # items()는 (key, value) 쌍을 가져옴
value_a = sorted(a.items(), key=lambda x: x[1])
# 딕셔너리의 key 값으로 정렬하여 key 리스트 반환
print(new1_a) # [1, 2, 3, 4, 5]
# 딕셔너리의 value 값으로 정렬하여 key 리스트 반환
print(new2_a) # [5, 2, 3, 1, 4]
# 딕셔너리의 key 값으로 정렬하여 (key, value) 리스트 반환
print(key_a) # [(1, 'D'), (2, 'B'), (3, 'C'), (4, 'E'), (5, 'A')]
# 딕셔너리의 value 값으로 정렬하여 (key, value) 리스트 반환
print(value_a) # [(5, 'A'), (2, 'B'), (3, 'C'), (1, 'D'), (4, 'E')]
# 그 외 예시들
words = ['b', 'a', 'bb', 'aaa', 'c']
# 람다를 사용하지 않은 예시 (길이 순으로 정렬)
words_1 = sorted(words, key=len)
print(words_1) # ['b', 'a', 'c', 'bb', 'aaa']
# 람다를 사용하여 두 개의 조건을 정해준 예시 (기준 1: 길이, 기준 2: 오름차순)
words_2 = sorted(words, key=lambda x: (len(x), x))
print(words_2) # ['a', 'b', 'c', 'bb', 'aaa']
# 조건에 '-'를 붙여 반대의 순서로 정렬
a = {3: 'C', 2: 'B', 1: 'D', 4: 'E', 5: 'A'}
key_a1 = sorted(a.items(), key=lambda x: x[0])
key_a2 = sorted(a.items(), key=lambda x: -x[0])
print(key_a1) # [(1, 'D'), (2, 'B'), (3, 'C'), (4, 'E'), (5, 'A')]
print(key_a2) # [(5, 'A'), (4, 'E'), (3, 'C'), (2, 'B'), (1, 'D')]
⏸ 정렬의 안정성
sort()와 sorted() 함수 모두 안정적(Stability)임이 보장됨
정렬에서 안정적이라는 말은 여러 레코드가 같은 키를 가질 때, 원래의 순서가 유지됨을 의미함!!
아래의 예시는 튜플의 [0] 값을 조건으로 오름차순한 정렬 결과임
# operator 모듈 함수 중 itemgetter() 사용
from operator import itemgetter
data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
print(sorted(data, key=itemgetter(0))) # [('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]
data = [('blue', 1), ('red', 2), ('blue', 2), ('red', 1)]
print(sorted(data, key=itemgetter(0))) # [('blue', 1), ('blue', 2), ('red', 2), ('red', 1)]
결과를 확인해보면 조건에 따라 blue -> red 순서로 정렬이 되었고, 그 값이 같은 경우 기존의 순서를 유지한 상태로 정렬된 것을 확인할 수 있음 ('red', 2), ('red', 1)
✅ 결론
- sort()와 sorted() 함수의 차이점 두 가지
◽ 원본 리스트 수정 / 새로운 리스트 반환
◽ 리스트에만 사용 가능 / 리스트 포함 Iterable 자료형에 사용 가능 - reverse의 기본값은 False이며, 오름차순 정렬이다.
- key 매개 변수를 통해 조건을 설정해 줄 수 있으며, lambda 식을 활용하면 더 간편하다.
- 정렬은 안정성(기존 순서 유지)이 보장된다.
'CodingTest > Python Grammar Notes' 카테고리의 다른 글
[Python] 10진수 -> 2, 8, 16진수 변환 (0) | 2023.01.14 |
---|---|
[Python] 대문자와 소문자 (반환, 검사) (0) | 2023.01.11 |
[Python] 에러와 예외 (try, except, raise ..) (1) | 2022.10.18 |
[Python] 정규 표현식 (Regular Expression, RE) (2) | 2022.10.11 |
[Python] 얕은 복사, 깊은 복사 (0) | 2022.07.26 |