[Python] 정렬 마스터하기: 주요 함수와 모듈 소개
기본 key 함수를 사용한 정렬부터 여러 기준으로 정렬하기, 비교 함수, operator 모듈, 사용자 정의 클래스를 활용한 정렬 방법까지 소개
![[Python] 정렬 마스터하기: 주요 함수와 모듈 소개](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1708053698604%2F9d1cadf3-414f-4eb6-9242-b3a1095e338f.png&w=3840&q=75)
Search for a command to run...
기본 key 함수를 사용한 정렬부터 여러 기준으로 정렬하기, 비교 함수, operator 모듈, 사용자 정의 클래스를 활용한 정렬 방법까지 소개
![[Python] 정렬 마스터하기: 주요 함수와 모듈 소개](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1708053698604%2F9d1cadf3-414f-4eb6-9242-b3a1095e338f.png&w=3840&q=75)
No comments yet. Be the first to comment.
한강 위 세빛둥둥섬에서의 11시간

개발자라면 누구나 한 번쯤 오픈소스 기여에 대한 환상을 가져본 적 있을 거다. 하지만 막상 시작하려면 어디서부터 해야 할지 막막하고, 괜히 대단한 걸 해야 할 것 같은 부담감에 선뜻 시작하기는 어려운 것 같다. 나 또한 해보고 싶다는 마음만 가지고 계속 미뤄왔다. 그러다 2025년 말 쯤에 오픈채팅방과 글또 슬랙 채널에서 "오픈소스 기여모임" 10기 모집글을 봤다. 2년 넘게 500명 이상의 참가자와 함께 1000개 이상의 PR을 만들어온 커뮤...
들어가며 드디어 글또 10기 활동 회고를 정리해본다.6개월간의 여정을 뒤돌아보니 정말 많은 일들이 있었다. 글또라는 커뮤니티를 8기가 한창 진행되고 있을 때 알았는데 이름부터 인상이 강렬했다. "글쓰는 또라이가 세상을 바꾼다." 유쾌하고 독특한 문구에 피식 웃으며, '여긴 도대체 어떤 사람들이 모이는 곳이지?' 하고 넘겼었다. 재밌는 건 결국, 나도 그 "또라이들" 중 한 명이 되었다는 것이다. 😌 글또는 개발자들이 2주에 한 번 글을 ...

들어가며 배포 직후, 환경변수가 제대로 설정되지 않아 여러 API가 제대로 작동하지 않는 일이 있었습니다. 다행히 밤에 사용자가 없을 때 문제가 있었던 거라 영향도는 크지 않았지만 앞으로도 계속해서 발생할 수 있는 문제이기 때문에 해결해야 겠다고 생각했습니다. 개발 단계에서 문제가 발견되면 가장 좋겠지만, 현재 팀 상황에서는 백엔드 개발을 혼자 담당하고 있어 코드 리뷰나 검증 프로세스를 갖추기가 쉽지 않았습니다. 그래서 최소한 배포 전에 자동으...
이 글에서 다루는 내용 포트포워딩이 안 되는 이유 (CGNAT 환경 이해) CGNAT 우회 방법들의 장단점 비교 Cloudflare Tunnel 설정 방법 (MacOS 기준) 외부에서 내 PC로 접근할 수 있도록 허용하는 방법을 생각하면 포트포워딩이 가장 먼저 떠오릅니다. 공유기에서 특정 포트를 열어 외부에서 서버에 접속할 수 있도록 설정하는 방식으로, 마인크래프트 멀티를 해보셨던 분이라면 분명 해보셨을 방법입니다. 😊 작년에 저는 홈서...

최근 프로그래머스에서 Python을 사용해 알고리즘 문제를 풀다가 정렬이 필요한 문제를 만났다. Python에서는 key함수를 이용해서 정렬을 하는데 다른 언어처럼 비교 함수를 구현해 정렬할 수 있는지 궁금해져서 공부를 하게 됐다. 이 글에서는 Python에서 정렬하는 방법을 알아본다.
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 모듈의 사전 정의된 기능을 활용할 수도 있다. 예를 들어, 리스트의 인덱스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로 이를 키 함수로 변환하고, sorted의 key 인자로 전달해 문자열 길이 기준으로 정렬한다.
복잡한 정렬 로직이 필요한 경우 사용자 정의 클래스를 만들어 정렬할 수 있도록 하는 것이 유지보수와 가독성 측면에서 유리하다.
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의 새로운 기능들이나 어떻게 업데이트가 되어왔는지 한 번 훑어보는 글을 작성해봐야겠다.