Book/파이썬 코딩의 기술

[BW 14] 복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라

31514 2024. 10. 22. 16:11

다음과 같은 클래스가 있다고 가정해보고 tools 를 정의했다.

class Tool:
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight
    

    def __repr__(self):
        return f'Tool({self.name!r}, {self.weight})'

tools = [
    Tool('수준계', 3.5),
    Tool('해머', 1.25),
    Tool('스크류드라이버', 0.25),
    Tool('끌', 0.25),
]

tools 에 담긴 여러 인스턴스를 sort()를 통해 정렬할 수 있을까?

비교 연산자를 지원하지 않는 오류가 발생한다.

Traceback (most recent call last):
  File "/Users/dev/Study/devcourse_aws/test.py", line 18, in <module>
    tools.sort()
TypeError: '<' not supported between instances of 'Tool' and 'Tool'

하지만 정렬 기준으로 객체의 속성을 전달한다면 비교 가능하다.

tools.sort(key=lambda x: x.name)
print(tools)

>>>
[Tool('끌', 0.25), Tool('수준계', 3.5), Tool('스크류드라이버', 0.25), Tool('해머', 1.25)]

만약 여러 기준을 사용해 정렬해야 한다면 어떻게 해야할까?

튜플을 사용하면 가장 쉽게 할 수 있다.

drill = (4, '드릴')
hammer = (4, '해머')
print(drill < hammer)

>>>
True

튜플을 활용하면 다음과 같이 sort 에도 적용할 수 있다.

tools.sort(key=lambda x: (x.weight, x.name))
print(tools)

>>>
[Tool('끌', 0.25), Tool('스크류드라이버', 0.25), Tool('해머', 1.25), Tool('수준계', 3.5)]

한 가지 제약 사항은 모든 비교 기준의 정렬 순서가 같아야 한다는 점이다.

예를 들어, x.weight에만 reverse를 걸 수 없다.

reverse를 걸면 x.weight과 x.name 모두 걸린다.

tools.sort(key=lambda x: (x.weight, x.name), reverse=True)
print(tools)

>>>
[Tool('수준계', 3.5), Tool('해머', 1.25), Tool('스크류드라이버', 0.25), Tool('끌', 0.25)]

x.weight에만 reverse를 걸고 싶다면 두 가지 방법이 있다.

첫 번째는 x.weight이 숫자이므로 부호 반전을 사용하는 것이다.

tools.sort(key=lambda x: (-x.weight, x.name))
print(tools)

>>>
[Tool('수준계', 3.5), Tool('해머', 1.25), Tool('끌', 0.25), Tool('스크류드라이버', 0.25)]

두 번째는 파이썬이 제공하는 안정적인 정렬 알고리즘을 사용하는 것이다.

리스트 타입의 sort 메서드는 key 함수가 반환하는 값이 서로 같은 경우 리스트에 들어 있던 원래의 순서를 그대로 유지한다.

tools.sort(key=lambda x: x.name)
tools.sort(key=lambda x: x.weight, reverse=True)
print(tools)

>>>
[Tool('수준계', 3.5), Tool('해머', 1.25), Tool('끌', 0.25), Tool('스크류드라이버', 0.25)]

하지만 부호 반전을 사용할 수 있다면 가독성을 위해 선호한다.