정렬 코드를 읽을 때는 결과의 순서와 원본 변경 여부를 따로 확인해야 한다. list.sort()는 원본 리스트를 정렬하고 None을 반환한다. sorted()는 정렬된 새 리스트를 반환한다.
numbers = [4, 1, 3]
ordered = sorted(numbers)
print(numbers)
print(ordered)
result = numbers.sort()
print(numbers)
print(result)
[4, 1, 3]
[1, 3, 4]
[1, 3, 4]
None
numbers = numbers.sort()로 작성하면 정렬된 리스트가 아니라 None이 변수에 저장된다. 원본을 유지해야 하는 경우에는 sorted(numbers)가 의도를 명확하게 드러낸다.
다음 데이터는 (이름, 점수) 형식이다. 점수 내림차순으로 정렬하고, 점수가 같으면 이름 오름차순으로 정렬한다.
students = [("Min", 90), ("Ara", 90), ("Jun", 80)]
ranked = sorted(students, key=lambda row: (-row[1], row[0]))
print(ranked)
print(students)
[('Ara', 90), ('Min', 90), ('Jun', 80)]
[('Min', 90), ('Ara', 90), ('Jun', 80)]
비교 키는 차례로 (-90, 'Min'), (-90, 'Ara'), (-80, 'Jun')이다. 튜플의 첫 항목을 먼저 비교하고 같으면 다음 항목을 비교한다. 점수에 음수를 붙여 점수만 내림차순으로 만들었다. 이 방법은 여기처럼 수치형 점수에 적용한다.
전체 키에 reverse=True를 적용하면 이름까지 내림차순이 된다. 여러 기준의 방향이 다를 때는 각 기준을 분리해서 생각해야 한다.
students = [("Min", 90), ("Ara", 90), ("Jun", 80)]
print(sorted(students, key=lambda row: row[1], reverse=True))
[('Min', 90), ('Ara', 90), ('Jun', 80)]
점수만 비교했으므로 같은 90점인 Min과 Ara는 입력 순서를 유지한다. 이것이 안정 정렬이다. 다만 안정 정렬이 이름순까지 보장하는 것은 아니다. 이름순이 요구사항이라면 앞의 예제처럼 두 번째 비교 기준을 명시해야 한다.
sorted()가 새 리스트를 만든다고 해서 내부의 가변 객체까지 모두 복제하는 것은 아니다. 이번 예제의 원소는 튜플이지만, 원소가 딕셔너리나 리스트라면 정렬 전후의 리스트가 같은 내부 객체를 참조할 수 있다. 원본의 순서 보존과 내부 데이터의 독립성은 별개의 조건이다.
정렬 전에 원본 변경 허용 여부, 첫 번째 기준, 동률일 때의 기준을 정한다. sort()와 sorted()의 선택은 첫 번째 조건에, key의 구성은 나머지 두 조건에 대응한다.