Skip to main content

Command Palette

Search for a command to run...

[Python] 정렬 마스터하기: 주요 함수와 모듈 소개

기본 key 함수를 사용한 정렬부터 여러 기준으로 정렬하기, 비교 함수, operator 모듈, 사용자 정의 클래스를 활용한 정렬 방법까지 소개

Updated
5 min read
[Python] 정렬 마스터하기: 주요 함수와 모듈 소개

최근 프로그래머스에서 Python을 사용해 알고리즘 문제를 풀다가 정렬이 필요한 문제를 만났다. Python에서는 key함수를 이용해서 정렬을 하는데 다른 언어처럼 비교 함수를 구현해 정렬할 수 있는지 궁금해져서 공부를 하게 됐다. 이 글에서는 Python에서 정렬하는 방법을 알아본다.

프로그래머스 - 가장 큰 수

Key 함수로 리스트 정렬하기

Python에서 가장 간단한 정렬 방법 중 하나는 key 함수를 사용하는 것이다. key 함수는 인자 하나를 받아 정렬에 사용할 기준 값을 반환한다.

key 함수, 또는 collation 함수는 min(), max(), sorted(), list.sort(), heapq.merge(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() 등 정렬이 필요한 다양한 함수에서 활용된다.

예제: 대소문자 구분 없는 정렬

words = ["apple", "Banana", "cherry", "Date"]
sorted_case_insensitive = sorted(words, key=str.lower)
sorted_default = sorted(words)

print(sorted_case_insensitive)
print(sorted_default)

# 출력 결과:
# ['apple', 'Banana', 'cherry', 'Date']
# ['Banana', 'Date', 'apple', 'cherry']

str.lower 함수를 key로 사용하면 대소문자를 구분하지 않고 알파벳 순으로 정렬한다. 반면, 기본 정렬은 대소문자를 구분해 정렬한다.

예제: 튜플의 특정 값 기준 정렬

# 학생 정보 (이름, 성적, 나이) 튜플 리스트
students = [
    ('John', 'A', 15),
    ('Dave', 'B', 15),
    ('Alice', 'A', 12),
    ('Carol', 'B', 12),
]
sorted_students_by_age = sorted(students, key=lambda student: student[2])

print(sorted_students_by_age)

# 출력 결과:
# [('Alice', 'A', 12), ('Carol', 'B', 12), ('John', 'A', 15), ('Dave', 'B', 15)]

나이를 기준으로 정렬하고 싶다면 key 함수에 나이에 해당하는 값을 반환하는 함수를 지정해 정렬하면 된다. 이 예제에서는 익명 함수를 사용해서 나이를 반환하는 함수를 구현했다.

예제: 여러 기준으로 정렬하기

Python 정렬 알고리즘은 기본적으로 안정적(stable)이어서, 같은 키 값을 가진 요소들 사이의 원래 순서를 유지한다. 여러 조건을 사용해 정렬할 때는 key 함수를 통해 반환되는 값을 튜플로 구성해 이를 쉽게 달성할 수 있다.

다음 예제에서는 학생 정보를 담은 튜플 리스트를 성적(내림차순)과 나이(오름차순)를 기준으로 정렬한다. 이를 위해 lambda 함수를 사용해 정렬 기준을 정의한다.

students = [('Carol', 'B', 12), ('Alice', 'A', 12), ('Dave', 'B', 15), ('John', 'A', 15)]
sorted_students = sorted(students, key=lambda student: (-ord(student[1]), student[2]))
print(sorted_students)
# 출력 결과:
# [('Alice', 'A', 12), ('John', 'A', 15), ('Carol', 'B', 12), ('Dave', 'B', 15)]

위 예제에서 lambda 함수는 각 학생(student)에 대해 성적의 알파벳(student[1])을 ASCII 코드로 변환해 내림차순 정렬 기준으로 하고, 같은 성적을 가진 학생들 사이에서는 나이(student[2])를 오름차순 정렬 기준으로 한다.

또 다른 방법으로 성적을 기준으로 내림차순 정렬한 후, 이후에 나이를 기준으로 오름차순 정렬하는 방식도 가능하다. 하지만, Python 정렬이 안정적이라는 점을 활용해 한 번에 여러 조건을 적용하는 것이 코드를 간결하게 유지하고 성능 측면에서도 유리하다.

# 성적 기준 내림차순 정렬 (단계 1)
sorted_by_grade = sorted(students, key=lambda student: -ord(student[1]))
# 나이 기준 오름차순으로 다시 정렬 (단계 2)
sorted_by_grade_then_age = sorted(sorted_by_grade, key=lambda student: student[2])
print(sorted_by_grade_then_age)
# 출력 결과:
# [('Alice', 'A', 12), ('John', 'A', 15), ('Carol', 'B', 12), ('Dave', 'B', 15)]

operator 모듈을 활용하여 리스트 정렬하기

함수를 임의로 정의할 수 있지만, operator 모듈의 사전 정의된 기능을 활용할 수도 있다. 예를 들어, 리스트의 인덱스1에 위치한 아이템을 가져오는 lambda 함수 대신 operator.itemgetter(1)를 사용할 수 있고, dictionary의 key나 클래스 멤버는 operator.attrgetter("firstname")와 같이 접근할 수 있다.

여러 기준으로 정렬하기를 원한다면 operator.itermgetter(1, 2) 또는 operator.attrgetter("firstname", "lastname")과 같이 사용할 수 있다.

import operator

sorted_students = sorted(students, key=operator.itemgetter(1, 2))
print(sorted_students)
# 출력 결과:
# [('Alice', 'A', 12), ('Carol', 'A', 15), ('Dave', 'B', 12), ('Bob', 'B', 15)]

operator.itemgetter(1, 2)를 사용해 성적 오름차순, 나이 오름차순으로 정렬한다.

from operator import attrgetter

class Person:
    def __init__(self, firstname, lastname):Ï
        self.firstname = firstname
        self.lastname = lastname

    def __repr__(self):
        return f"Person({self.firstname}, {self.lastname})"

persons = [
    Person('John', 'Doe'),
    Person('Jane', 'Doe'),
    Person('Alice', 'Cooper'),
    Person('Bob', 'Barker')
]

sorted_persons = sorted(persons, key=attrgetter('lastname', 'firstname'))
Ï
for person in sorted_persons:
    print(person)

# 출력 결과:
# Person(Bob, Barker)
# Person(Alice, Cooper)
# Person(Jane, Doe)
# Person(John, Doe)

클래스나 dictionary의 경우 operator.attrgetter(...)로 속성 기준 정렬을 한다.

functools.cmp_to_key로 리스트 정렬하기

functools.cmp_to_key 함수는 사용자 정의 비교 함수를 키 함수로 변환해 사용한다. Python 3에서는 cmp(a, b) 형태의 직접 비교 방식 대신 키 함수 사용을 권장한다. 비교 함수를 사용해야 하는 상황이라면 이 함수로 비교 함수를 키 함수로 변환할 수 있다.

from functools import cmp_to_key

def compare_items(x, y):
    return len(x) - len(y)

key_function = cmp_to_key(compare_items)

str_list = ['banana', 'apple', 'cherry', 'date']
sorted_str_list = sorted(str_list, key=key_function)

print(sorted_str_list)  

# 출력 결과:
# ['date', 'apple', 'banana', 'cherry']

compare_items 함수는 문자열 길이를 비교한다. cmp_to_key로 이를 키 함수로 변환하고, sortedkey 인자로 전달해 문자열 길이 기준으로 정렬한다.

정렬을 지원하는 클래스 만들기

복잡한 정렬 로직이 필요한 경우 사용자 정의 클래스를 만들어 정렬할 수 있도록 하는 것이 유지보수와 가독성 측면에서 유리하다.

Python에서는 매직 메소드를 이용하여 클래스 인스턴스 간 비교 연산자를 직접 정의할 수 있고 이렇게 구현한 비교 연산자들은 sorted, list.sort()와 같은 내장 함수와도 함께 사용할 수 있어 더 깨끗하고 Pythonnic한 코드를 만들 수 있다.

class Person:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def __repr__(self):
        return f"Person('{self.firstname}', '{self.lastname}')"

    # 비교 매직 메소드 추가
    def __lt__(self, other):
        return (self.lastname, self.firstname) < (other.lastname, other.firstname)

persons = [
    Person('John', 'Doe'),
    Person('Jane', 'Doe'),
    Person('Alice', 'Cooper'),
    Person('Bob', 'Barker')
]

# 성(lastname)과 이름(firstname) 순으로 정렬
sorted_persons = sorted(persons)

for person in sorted_persons:
    print(person)

# 출력 결과:
# Person('Bob', 'Barker')
# Person('Alice', 'Cooper')
# Person('Jane', 'Doe')
# Person('John', 'Doe')

결론

이번 포스팅을 작성하면서 python 정렬 방식을 확실하게 정리한 것 뿐만 아니라 operator 모듈을 활용하는 방법이나 여러 정렬 기준을 한 번에 적용하여 정렬하는 방법까지 알게 됐다. Python을 꽤 오래 사용했었는데 관성적으로 사용하는 것들만 사용하던 것을 반성하는 시간이 됐다.

오랜만에 어떤 기능들이 추가됐는지 궁금하여 Python 3.12 릴리즈 문서를 열어보니 type statement가 추가되어 generic 타입과 type aliases를 좀 더 자연스럽게 할 수 있게 됐고, f-strings 사용 시 불편했던 "{""}"이 이제는 오류가 나지 않는다고 해서 반가웠다. 언어도 끊임없이 발전하니 언어에 대한 공부도 게을리하지 말아야겠다고 생각이 들었다.

다음에는 최근에 많이 쓰고 있는 Dart나 Python의 새로운 기능들이나 어떻게 업데이트가 되어왔는지 한 번 훑어보는 글을 작성해봐야겠다.

References

More from this blog

오픈소스 기여모임 10기 후기 - 첫 Pr을 올리기까지

개발자라면 누구나 한 번쯤 오픈소스 기여에 대한 환상을 가져본 적 있을 거다. 하지만 막상 시작하려면 어디서부터 해야 할지 막막하고, 괜히 대단한 걸 해야 할 것 같은 부담감에 선뜻 시작하기는 어려운 것 같다. 나 또한 해보고 싶다는 마음만 가지고 계속 미뤄왔다. 그러다 2025년 말 쯤에 오픈채팅방과 글또 슬랙 채널에서 "오픈소스 기여모임" 10기 모집글을 봤다. 2년 넘게 500명 이상의 참가자와 함께 1000개 이상의 PR을 만들어온 커뮤...

Feb 5, 20265 min read

😢 글또 10기 활동 회고 — “글또야, 가지 마…”

들어가며 드디어 글또 10기 활동 회고를 정리해본다.6개월간의 여정을 뒤돌아보니 정말 많은 일들이 있었다. 글또라는 커뮤니티를 8기가 한창 진행되고 있을 때 알았는데 이름부터 인상이 강렬했다. "글쓰는 또라이가 세상을 바꾼다." 유쾌하고 독특한 문구에 피식 웃으며, '여긴 도대체 어떤 사람들이 모이는 곳이지?' 하고 넘겼었다. 재밌는 건 결국, 나도 그 "또라이들" 중 한 명이 되었다는 것이다. 😌 글또는 개발자들이 2주에 한 번 글을 ...

Jul 31, 20255 min read
😢 글또 10기 활동 회고 — “글또야, 가지 마…”

Serverless 환경에서 배포 전 환경변수 검증 자동화하기: TypeBox와 Bitbucket Pipeline 활용기

들어가며 배포 직후, 환경변수가 제대로 설정되지 않아 여러 API가 제대로 작동하지 않는 일이 있었습니다. 다행히 밤에 사용자가 없을 때 문제가 있었던 거라 영향도는 크지 않았지만 앞으로도 계속해서 발생할 수 있는 문제이기 때문에 해결해야 겠다고 생각했습니다. 개발 단계에서 문제가 발견되면 가장 좋겠지만, 현재 팀 상황에서는 백엔드 개발을 혼자 담당하고 있어 코드 리뷰나 검증 프로세스를 갖추기가 쉽지 않았습니다. 그래서 최소한 배포 전에 자동으...

Mar 16, 20254 min read

Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

이 글에서 다루는 내용 포트포워딩이 안 되는 이유 (CGNAT 환경 이해) CGNAT 우회 방법들의 장단점 비교 Cloudflare Tunnel 설정 방법 (MacOS 기준) 외부에서 내 PC로 접근할 수 있도록 허용하는 방법을 생각하면 포트포워딩이 가장 먼저 떠오릅니다. 공유기에서 특정 포트를 열어 외부에서 서버에 접속할 수 있도록 설정하는 방식으로, 마인크래프트 멀티를 해보셨던 분이라면 분명 해보셨을 방법입니다. 😊 작년에 저는 홈서...

Mar 2, 20256 min read
Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

구름고래 공방

48 posts

[Python] 정렬 마스터하기: 주요 함수와 모듈 소개