PEP 265 – 딕셔너리를 값 기준으로 정렬하기
- Author:
- Grant Griffin <g2 at iowegian.com>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 08-Aug-2001
- Python-Version:
- 2.2
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 딕셔너리에 대한 “값 기준 정렬” 연산을 제안합니다. 주된 이점은 흔히 쓰이는 Python 관용구를 “배터리 포함” 방식으로 지원한다는 점입니다. 이 관용구는 현재 형태로는 초보자가 이해하기 어렵고, 누구에게나 구현하기 번거롭습니다.
BDFL 선언
이 PEP는 Py2.4의 sorted() 내장 함수가 그 필요를 대부분 충족하므로 거부되었습니다:
>>> sorted(d.iteritems(), key=itemgetter(1), reverse=True)
[('b', 23), ('d', 17), ('c', 5), ('a', 2), ('e', 1)]
또는 키만 필요한 경우:
sorted(d, key=d.__getitem__, reverse=True)
['b', 'd', 'c', 'a', 'e']
또한 Python 2.5의 heapq.nlargest() 함수는 값이 가장 큰 항목 몇 개만 찾는 흔한 사용 사례를 해결합니다:
>>> nlargest(2, d.iteritems(), itemgetter(1))
[('b', 23), ('d', 17)]
동기
딕셔너리는 흔히 등장 횟수를 세는 데 사용됩니다. 어떤 키가 처음 등장하면 d[key]의 값을 1로 설정하고, 이후 등장할 때마다 그 값을 증가시키는 방식입니다. 이는 여러 가지 방법으로 할 수 있지만, get() 메서드를 사용하는 방법이 가장 간결합니다:
d[key] = d.get(key, 0) + 1
모든 등장 횟수를 센 뒤에는 결과 딕셔너리를 사용하여 등장 횟수 순으로, 흔히 가장 큰 값부터 출력하는 경우가 많습니다.
이 때문에 딕셔너리의 항목을 값 기준으로 정렬할 필요가 생깁니다. Python에서 이를 수행하는 정석적인 방법은 다음과 같습니다. 먼저 d.items()를 사용하여 딕셔너리 항목의 리스트를 얻고, 각 항목 튜플의 순서를 (key, value)에서 (value, key)로 뒤집은 다음, 리스트를 정렬합니다. Python은 튜플의 첫 번째 요소를 기준으로 리스트를 정렬하므로, (뒤집힌) 항목의 리스트는 결과적으로 값 기준으로 정렬됩니다. 원한다면 그 후 리스트를 역순으로 만들고, 튜플을 다시 (key, value)로 되돌릴 수 있습니다. (하지만 제 경험상 리스트를 출력하는 것과 같은 대부분의 용도에는 뒤집힌 튜플 순서로도 충분합니다.)
예를 들어, 다음과 같은 등장 횟수가 주어졌을 때:
>>> d = {'a':2, 'b':23, 'c':5, 'd':17, 'e':1}
다음과 같이 할 수 있으며:
>>> items = [(v, k) for k, v in d.items()]
>>> items.sort()
>>> items.reverse() # so largest is first
>>> items = [(k, v) for v, k in items]
그 결과는 다음과 같습니다:
>>> items
[('b', 23), ('d', 17), ('c', 5), ('a', 2), ('e', 1)]
이는 리스트를 값 기준으로, 가장 큰 값부터 보여 줍니다. (이 경우 'b'가 가장 많이 등장한 것으로 나타났습니다.)
이 방법은 잘 작동하지만, 두 가지 측면에서 “사용하기 어렵습니다”. 첫째, 이 관용구는 노련한 Python 사용자에게는 잘 알려져 있지만, 초보자에게는 전혀 명확하지 않습니다. 알고리즘(항목 튜플의 순서 뒤집기) 측면에서도, 구현(Python의 고급 기능인 리스트 컴프리헨션 사용) 측면에서도 그렇습니다. 둘째, 번거로운 “잡다한 코드”를 반복해서 많이 입력해야 하므로 지루하고 실수하기 쉽습니다.
따라서 우리는 Python이 딕셔너리를 값 기준으로 정렬하는 방법을 제공하기를 바랍니다. 이 방법은 초보자가 이해하기 쉽고(더 나아가 아예 이해할 필요가 없고), 누구에게나 더 사용하기 쉬워야 합니다.
근거
Tim Peters가 지적했듯이, 이런 종류의 기능은 모든 사람의 모든 요구를 충족하려 드는 문제를 불러옵니다. 따라서 “가장 적절한 지점”을 겨냥하도록 범위를 제한하겠습니다. 물론 특이한 경우(예: 사용자 정의 비교 함수를 이용한 정렬)는 현재의 방법을 사용하여 “수동으로” 처리할 수 있습니다.
다음은 몇 가지 간단한 방안입니다:
딕셔너리의 items() 메서드에 완전한 하위 호환성을 보장하는 기본값을 가진 새 매개변수를 추가할 수 있습니다:
(1) items(sort_by_values=0, reversed=0)
또는 단순히 다음과 같이 할 수도 있습니다:
(2) items(sort_by_values=0)
리스트를 역순으로 만드는 것은 충분히 쉽기 때문입니다.
또는 items()가 단순히 (key, value) 순서를 제어할 수 있게 해 줄 수도 있습니다:
(3) items(values_first=0)
다시 말하지만, 이 방법은 하위 호환성을 완전히 유지합니다. 다른 방법들보다 하는 일은 적지만, 적어도 값 기준 정렬 문제에서 가장 복잡하고 까다로운 부분인 항목 튜플의 순서 뒤집기를 수월하게 해 줍니다. 사용법은 매우 간단합니다:
items = d.items(1)
items.sort()
items.reverse() # (if desired)
앞의 세 가지 방법의 주된 단점은 기본 매개변수를 처리해야 하므로 매개변수 없이 items()를 호출하는 경우에 추가 오버헤드가 생긴다는 점입니다. (하지만 items()가 주로 값 기준으로 정렬된 리스트를 만드는 데 쓰인다고 가정한다면, 이는 실제로는 그다지 단점이 되지 않습니다.)
또는 어떤 식으로든 “정렬”을 구현하는 새로운 딕셔너리 메서드를 추가할 수도 있습니다. 이 방법에는 두 가지 장점이 있습니다. 첫째, items() 메서드에 오버헤드를 추가하지 않습니다. 둘째, 초보자가 접근하기에 아마 더 쉬울 것입니다. 초보자가 딕셔너리를 정렬하는 메서드를 찾다 보면 이 메서드를 발견하게 될 것이고, 값 기준 정렬을 하기 위해 튜플 뒤집기나 리스트 정렬의 세부 사항을 이해하지 않아도 됩니다.
키/값 기준 정렬과 정방향/역방향 순서라는 네 가지 기본 조합을 모두 지원하기 위해 다음 메서드를 추가할 수 있습니다:
(4) sorted_items(by_value=0, reversed=0)
실제로 가장 흔한 경우는 by_value=1, reversed=1일 것이라고 생각하지만, 여기에 제시한 기본값을 사용하면 사용자가 놀랄 일이 줄어들 것입니다. sorted_items()는 items() 다음에 sort()를 호출한 것과 같습니다.
마지막으로 (최후의 수단으로) 다음을 사용할 수 있습니다:
(5) items_sorted_by_value(reversed=0)
구현
제안한 딕셔너리 메서드는 반드시 C로 구현해야 합니다. 기존 Python 기능에 호출 몇 개만 추가하면 되므로, 구현은 아마 꽤 간단할 것입니다.
우려 사항
1번부터 3번까지의 방법에서 이미 다룬 실행 시간 오버헤드를 제외하면, 이 제안에 대한 우려는 대체로 “기능 비대화”나 “코드 비대화” 또는 둘 다에 해당할 것입니다. 하지만 여기에서 제시한 제안 중 몇 가지는 비대화를 아주 적게 일으키므로, 비대화와 “부가 가치” 사이에서 좋은 절충점이 될 것이라고 생각합니다.
Tim Peters는 이를 C로 구현해도 현재 Python으로 구현하는 것보다 크게 빠르지 않을 수 있다고 지적했습니다. 하지만 여기서 의도하는 주된 이점은 “속도”가 아니라 “접근성”과 “사용 편의성”입니다. 따라서 눈에 띄게 느려지지만 않는다면 (단순한 items()의 경우), 속도는 고려하지 않아도 됩니다.
참고 문헌
2001년 8월 comp.lang.python에 “counting occurrences”라는 관련 스레드가 올라왔습니다. 여기에는 값 기준 정렬 문제를 재사용 가능한 Python 함수와 클래스로 구현하여 체계화하는 접근 방식의 예가 포함되어 있었습니다.
Copyright
This document has been placed in the public domain.