Python 정렬: sorted(), list.sort(), 커스텀 정렬 완전 가이드
Updated on
데이터 정렬은 프로그래밍에서 가장 기본적인 작업 중 하나지만, Python의 sorted() 함수와 list.sort() 메서드 중 무엇을 선택해야 하는지는 많은 개발자를 헷갈리게 합니다. 학생 점수를 순위화하거나, 상품 가격을 정리하거나, 파일 목록을 처리하는 등 어떤 상황이든 Python의 정렬 메커니즘을 제대로 이해하면 시간을 절약하고 버그를 예방할 수 있습니다. 이 가이드는 기본 숫자 리스트 정렬부터 복잡한 커스텀 비교까지 Python 정렬의 모든 측면을 다루며, 바로 적용할 수 있는 실용적인 예제를 제공합니다.
sorted() vs list.sort(): 결정적인 차이
Python에는 겉보기엔 비슷하지만 동작 방식이 근본적으로 다른 두 가지 주요 정렬 방식이 있습니다. sorted() 함수는 새로운 정렬된 리스트를 생성해 반환하며 원본은 변경하지 않습니다. 반면 list.sort() 메서드는 리스트 자체를 제자리(in place)에서 변경하고 None을 반환합니다.
# sorted() returns a new list
numbers = [3, 1, 4, 1, 5]
sorted_nums = sorted(numbers)
print(sorted_nums) # [1, 1, 3, 4, 5]
print(numbers) # [3, 1, 4, 1, 5] - original unchanged
# list.sort() modifies in place
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(numbers) # [1, 1, 3, 4, 5] - original modified
print(result) # None이 차이는 메모리 효율과 데이터 보존 측면에서 중요합니다. 원본 리스트를 유지해야 하거나 튜플처럼 변경 불가능한 시퀀스를 정렬해야 한다면 sorted()를 사용하세요. 반대로 큰 리스트에서 복사본을 만드는 것이 과도한 메모리를 소비한다면 list.sort()가 적합합니다.
# sorted() works with any iterable
tuple_data = (42, 17, 93, 8)
sorted_list = sorted(tuple_data) # Returns [8, 17, 42, 93]
# list.sort() only works on lists
# tuple_data.sort() # AttributeError: 'tuple' object has no attribute 'sort'기본 정렬: 숫자, 문자열, 혼합 타입
Python의 기본 정렬 동작은 자연스러운 순서를 사용합니다. 숫자는 수치 기준으로 정렬되고, 문자열은 알파벳(사전식) 순으로 정렬되며, 서로 다른 타입이 섞인 리스트는 명시적 처리가 필요합니다.
# Numeric sorting
integers = [42, -7, 0, 93, 15]
print(sorted(integers)) # [-7, 0, 15, 42, 93]
floats = [3.14, 2.71, 1.41, 9.81]
print(sorted(floats)) # [1.41, 2.71, 3.14, 9.81]
# String sorting (lexicographic order)
words = ['zebra', 'apple', 'mango', 'banana']
print(sorted(words)) # ['apple', 'banana', 'mango', 'zebra']
# Mixed types cause errors in Python 3
mixed = [3, 'apple', 1.5]
# sorted(mixed) # TypeError: '<' not supported between instances숫자 문자열을 알파벳 순이 아니라 숫자 순으로 정렬하고 싶다면, 정렬 중에 변환을 적용하면 됩니다.
# String numbers sort alphabetically
string_nums = ['10', '2', '100', '21']
print(sorted(string_nums)) # ['10', '100', '2', '21']
# Convert to int for numeric sorting
print(sorted(string_nums, key=int)) # ['2', '10', '21', '100']reverse=True로 역순 정렬
sorted()와 list.sort() 모두 정렬 방향을 뒤집는 reverse 파라미터를 받습니다. 기본값은 False이며 오름차순 정렬입니다.
scores = [88, 92, 75, 95, 82]
# Descending order using reverse=True
print(sorted(scores, reverse=True)) # [95, 92, 88, 82, 75]
# In-place descending sort
scores.sort(reverse=True)
print(scores) # [95, 92, 88, 82, 75]
# Reverse alphabetical order
names = ['Alice', 'David', 'Bob', 'Charlie']
print(sorted(names, reverse=True)) # ['David', 'Charlie', 'Bob', 'Alice']key 파라미터: 커스텀 정렬 로직
key 파라미터는 각 원소를 비교하기 전에 변환하는 함수를 받습니다. 이를 통해 원본 데이터를 바꾸지 않고도 특정 속성, 계산된 값, 복잡한 기준으로 정렬할 수 있습니다.
# Sort by string length
words = ['python', 'is', 'awesome', 'for', 'data']
print(sorted(words, key=len)) # ['is', 'for', 'data', 'python', 'awesome']
# Sort by absolute value
numbers = [-5, -2, 3, -8, 1]
print(sorted(numbers, key=abs)) # [1, -2, 3, -5, -8]
# Sort case-insensitive
names = ['alice', 'Bob', 'CHARLIE', 'david']
print(sorted(names, key=str.lower)) # ['alice', 'Bob', 'CHARLIE', 'david']Lambda 함수로 인라인 정렬
Lambda 함수는 간단한 key 함수를 짧게 표현할 수 있어, 한 번만 쓰는 변환 로직에 특히 유용합니다.
# Sort tuples by second element
pairs = [(1, 'b'), (3, 'a'), (2, 'd'), (4, 'c')]
print(sorted(pairs, key=lambda x: x[1]))
# [(3, 'a'), (1, 'b'), (4, 'c'), (2, 'd')]
# Sort strings by last character
words = ['python', 'java', 'ruby', 'go']
print(sorted(words, key=lambda s: s[-1]))
# ['java', 'go', 'ruby', 'python']
# Sort by distance from target value
target = 50
values = [45, 62, 38, 55, 48]
print(sorted(values, key=lambda x: abs(x - target)))
# [48, 45, 55, 38, 62]여러 기준으로 정렬하기
정렬에 우선순위가 여러 개라면 key 함수에서 튜플을 반환하면 됩니다. Python은 튜플을 왼쪽부터 순서대로 비교하므로, 자연스럽게 다단계 정렬이 됩니다.
# Sort students by grade (descending), then name (ascending)
students = [
('Alice', 85),
('Bob', 92),
('Charlie', 85),
('David', 92)
]
sorted_students = sorted(students, key=lambda x: (-x[1], x[0]))
print(sorted_students)
# [('Bob', 92), ('David', 92), ('Alice', 85), ('Charlie', 85)]
# Sort dates by year (descending), then month (ascending)
dates = ['2024-03', '2023-12', '2024-01', '2023-08']
print(sorted(dates, key=lambda d: (-int(d[:4]), int(d[5:]))))
# ['2024-01', '2024-03', '2023-08', '2023-12']operator.itemgetter와 operator.attrgetter
operator 모듈은 흔한 정렬 패턴에서 lambda를 대체하는 최적화된 도구를 제공합니다. lambda보다 실행이 빠른 편이고, 코드 가독성도 좋아집니다.
from operator import itemgetter, attrgetter
# Sort by specific dictionary keys
data = [
{'name': 'Alice', 'age': 30, 'score': 85},
{'name': 'Bob', 'age': 25, 'score': 92},
{'name': 'Charlie', 'age': 30, 'score': 78}
]
# Sort by age, then score
sorted_data = sorted(data, key=itemgetter('age', 'score'))
print([d['name'] for d in sorted_data]) # ['Bob', 'Charlie', 'Alice']
# Sort by tuple elements
records = [(1, 'b', 300), (2, 'a', 100), (1, 'a', 200)]
print(sorted(records, key=itemgetter(0, 1)))
# [(1, 'a', 200), (1, 'b', 300), (2, 'a', 100)]객체 속성 기준 정렬에는 attrgetter를 사용합니다.
from operator import attrgetter
class Employee:
def __init__(self, name, salary, department):
self.name = name
self.salary = salary
self.department = department
def __repr__(self):
return f'{self.name}({self.department}, ${self.salary})'
employees = [
Employee('Alice', 75000, 'Engineering'),
Employee('Bob', 65000, 'Marketing'),
Employee('Charlie', 75000, 'Engineering')
]
# Sort by department, then salary (descending)
sorted_emps = sorted(employees, key=attrgetter('department'))
print(sorted_emps)딕셔너리 정렬
딕셔너리 자체는 전통적인 의미에서 “정렬”되는 구조는 아니지만(Python 3.7+는 삽입 순서를 유지), key/value/items를 정렬할 수는 있습니다.
# Sort dictionary by keys
data = {'zebra': 3, 'apple': 1, 'mango': 2}
sorted_keys = sorted(data.keys())
print(sorted_keys) # ['apple', 'mango', 'zebra']
# Create new dict with sorted keys
sorted_dict = {k: data[k] for k in sorted_keys}
print(sorted_dict) # {'apple': 1, 'mango': 2, 'zebra': 3}
# Sort by values
sorted_by_value = sorted(data.items(), key=lambda x: x[1])
print(sorted_by_value) # [('apple', 1), ('mango', 2), ('zebra', 3)]
# Convert back to dictionary
sorted_dict = dict(sorted_by_value)값 타입이 복잡한 경우:
# Sort dictionary by nested values
products = {
'laptop': {'price': 999, 'stock': 5},
'phone': {'price': 599, 'stock': 12},
'tablet': {'price': 399, 'stock': 8}
}
# Sort by price
by_price = sorted(products.items(), key=lambda x: x[1]['price'])
print([(name, info['price']) for name, info in by_price])
# [('tablet', 399), ('phone', 599), ('laptop', 999)]
# Sort by stock (descending)
by_stock = sorted(products.items(), key=lambda x: -x[1]['stock'])
print([(name, info['stock']) for name, info in by_stock])
# [('phone', 12), ('tablet', 8), ('laptop', 5)]튜플 리스트 정렬
튜플은 왼쪽에서 오른쪽으로 원소를 비교하는 방식으로 자연 정렬됩니다. key 함수를 통해 동작을 바꿀 수 있습니다.
# Default tuple sorting (compares element by element)
tuples = [(1, 'z'), (2, 'a'), (1, 'a'), (2, 'z')]
print(sorted(tuples))
# [(1, 'a'), (1, 'z'), (2, 'a'), (2, 'z')]
# Sort by second element only
print(sorted(tuples, key=lambda x: x[1]))
# [(2, 'a'), (1, 'a'), (1, 'z'), (2, 'z')]
# Sort by sum of numeric elements
coord_distances = [(1, 2), (3, 1), (2, 2), (1, 3)]
print(sorted(coord_distances, key=sum))
# [(1, 2), (2, 2), (1, 3), (3, 1)]딕셔너리 리스트 정렬
리스트 안의 딕셔너리를 정렬하려면 비교에 사용할 key를 지정해야 합니다.
people = [
{'name': 'Alice', 'age': 30, 'city': 'New York'},
{'name': 'Bob', 'age': 25, 'city': 'San Francisco'},
{'name': 'Charlie', 'age': 30, 'city': 'Austin'}
]
# Sort by age
by_age = sorted(people, key=lambda x: x['age'])
print([p['name'] for p in by_age]) # ['Bob', 'Alice', 'Charlie']
# Sort by age (descending), then name (ascending)
by_age_name = sorted(people, key=lambda x: (-x['age'], x['name']))
print([f"{p['name']} ({p['age']})" for p in by_age_name])
# ['Alice (30)', 'Charlie (30)', 'Bob (25)']
# Using itemgetter (more efficient)
from operator import itemgetter
by_city = sorted(people, key=itemgetter('city'))
print([p['city'] for p in by_city])
# ['Austin', 'New York', 'San Francisco']객체 리스트 정렬
커스텀 클래스는 비교 가능한 속성을 추출하는 key 함수가 필요합니다.
class Product:
def __init__(self, name, price, rating):
self.name = name
self.price = price
self.rating = rating
def __repr__(self):
return f'{self.name}(${self.price}, {self.rating}★)'
products = [
Product('Laptop', 999, 4.5),
Product('Phone', 599, 4.8),
Product('Tablet', 399, 4.2),
Product('Monitor', 299, 4.7)
]
# Sort by price
by_price = sorted(products, key=lambda p: p.price)
print(by_price)
# Sort by rating (descending)
by_rating = sorted(products, key=lambda p: -p.rating)
print(by_rating)
# Sort by value score (rating / price * 100)
by_value = sorted(products, key=lambda p: (p.rating / p.price) * 100, reverse=True)
print(by_value)또는 기본 정렬 동작을 위해 __lt__(less than) 메서드를 구현할 수도 있습니다.
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
def __lt__(self, other):
return self.gpa > other.gpa # Higher GPA comes first
def __repr__(self):
return f'{self.name}({self.gpa})'
students = [Student('Alice', 3.8), Student('Bob', 3.9), Student('Charlie', 3.7)]
print(sorted(students)) # [Bob(3.9), Alice(3.8), Charlie(3.7)]대소문자 구분 없는 문자열 정렬
문자열 비교는 기본적으로 대소문자를 구분하므로, 대문자가 소문자보다 먼저 정렬되는 일이 발생합니다. 대소문자 구분 없는 정렬에는 str.lower() 또는 str.casefold()를 사용하세요.
# Case-sensitive sorting (default)
words = ['apple', 'Banana', 'cherry', 'Date']
print(sorted(words)) # ['Banana', 'Date', 'apple', 'cherry']
# Case-insensitive sorting
print(sorted(words, key=str.lower)) # ['apple', 'Banana', 'cherry', 'Date']
# Using casefold for Unicode handling
international = ['café', 'CAFÉ', 'Apple', 'apple']
print(sorted(international, key=str.casefold))안정 정렬 보장과 Timsort 알고리즘
Python은 **안정 정렬(stable sort)**을 보장하는 Timsort 알고리즘을 사용합니다. 안정성이란 key 값이 같은 원소들의 상대적 순서가 원래대로 유지된다는 뜻입니다.
# Stable sort preserves original order for equal elements
data = [
('Alice', 85),
('Bob', 92),
('Charlie', 85), # Same score as Alice
('David', 92) # Same score as Bob
]
# Sort by score only - original order preserved for ties
by_score = sorted(data, key=lambda x: x[1])
print(by_score)
# [('Alice', 85), ('Charlie', 85), ('Bob', 92), ('David', 92)]
# Alice appears before Charlie (both 85) because she came first이 안정성 덕분에 다단계 정렬을 여러 번에 나눠 수행할 수 있습니다.
# Multi-pass sorting using stability
records = [
{'name': 'Alice', 'dept': 'HR', 'salary': 60000},
{'name': 'Bob', 'dept': 'IT', 'salary': 75000},
{'name': 'Charlie', 'dept': 'HR', 'salary': 65000},
{'name': 'David', 'dept': 'IT', 'salary': 70000}
]
# First sort by salary
records.sort(key=lambda x: x['salary'], reverse=True)
# Then sort by department (stable, so salary order preserved within dept)
records.sort(key=lambda x: x['dept'])
for r in records:
print(f"{r['dept']}: {r['name']} (${r['salary']})")
# HR: Charlie ($65000)
# HR: Alice ($60000)
# IT: Bob ($75000)
# IT: David ($70000)성능 비교: 정렬 방법들
Python 정렬은 최악의 경우 시간 복잡도가 O(n log n)이며, 대부분의 데이터셋에서 효율적입니다. 다만 상황에 따라 더 적합한 접근이 있습니다.
| Method | Time Complexity | Space | Use Case |
|---|---|---|---|
list.sort() | O(n log n) | O(1) | 제자리 정렬, 큰 리스트 |
sorted() | O(n log n) | O(n) | 원본 보존, 모든 iterable 정렬 |
heapq.nsmallest(k, items) | O(n log k) | O(k) | 큰 데이터셋에서 상위 k개 추출 |
heapq.nlargest(k, items) | O(n log k) | O(k) | 큰 데이터셋에서 하위 k개 추출 |
bisect.insort(list, item) | O(n) | O(1) | 삽입이 잦은 정렬 상태 리스트 유지 |
import heapq
# When you only need top 3 scores from 1 million records
scores = list(range(1000000))
top_3 = heapq.nlargest(3, scores) # Much faster than sorted()[-3:]
print(top_3) # [999999, 999998, 999997]
# When you need bottom 5 values
bottom_5 = heapq.nsmallest(5, scores)
print(bottom_5) # [0, 1, 2, 3, 4]삽입이 빈번한 경우 정렬된 리스트를 유지하려면:
import bisect
# Maintain sorted list efficiently
sorted_list = []
for value in [5, 2, 8, 1, 9, 3]:
bisect.insort(sorted_list, value)
print(sorted_list) # [1, 2, 3, 5, 8, 9]레거시 비교 함수용 functools.cmp_to_key
Python 2에서는 -1, 0, 1을 반환하는 비교 함수(comparison function)를 사용했습니다. Python 3에서는 key 함수가 필요하지만, functools.cmp_to_key로 예전 방식 비교 함수를 변환할 수 있습니다.
from functools import cmp_to_key
# Custom comparison: sort by remainder when divided by 3
def compare_mod3(x, y):
return (x % 3) - (y % 3)
numbers = [10, 11, 12, 13, 14, 15]
sorted_nums = sorted(numbers, key=cmp_to_key(compare_mod3))
print(sorted_nums) # [12, 15, 10, 13, 11, 14]
# Groups: [12,15] (mod 0), [10,13] (mod 1), [11,14] (mod 2)
# Complex comparison: version strings
def compare_versions(v1, v2):
parts1 = list(map(int, v1.split('.')))
parts2 = list(map(int, v2.split('.')))
for p1, p2 in zip(parts1, parts2):
if p1 != p2:
return p1 - p2
return len(parts1) - len(parts2)
versions = ['1.10.2', '1.9.0', '1.10.15', '2.0.0', '1.10']
sorted_versions = sorted(versions, key=cmp_to_key(compare_versions))
print(sorted_versions) # ['1.9.0', '1.10', '1.10.2', '1.10.15', '2.0.0']None 값이 있는 경우 정렬
None은 다른 타입과 섞여 있을 때 비교 에러를 일으킵니다. None을 앞/뒤로 보내도록 명시적으로 처리하세요.
# None values cause errors
data = [3, None, 1, 5, None, 2]
# sorted(data) # TypeError: '<' not supported between 'NoneType' and 'int'
# Place None values at the end
sorted_data = sorted(data, key=lambda x: (x is None, x))
print(sorted_data) # [1, 2, 3, 5, None, None]
# Place None values at the beginning
sorted_data = sorted(data, key=lambda x: (x is not None, x))
print(sorted_data) # [None, None, 1, 2, 3, 5]
# Replace None with specific value for sorting
sorted_data = sorted(data, key=lambda x: x if x is not None else float('-inf'))
print(sorted_data) # [None, None, 1, 2, 3, 5]키가 없을 수 있는 딕셔너리의 경우:
records = [
{'name': 'Alice', 'score': 85},
{'name': 'Bob'}, # Missing score
{'name': 'Charlie', 'score': 92},
{'name': 'David'} # Missing score
]
# Sort with default value for missing keys
sorted_records = sorted(records, key=lambda x: x.get('score', 0))
print([r['name'] for r in sorted_records])
# ['Bob', 'David', 'Alice', 'Charlie']실전 예시: 학생 성과 랭킹 만들기
이 예시는 여러 정렬 기법을 결합해 종합 랭킹 시스템을 만드는 방법을 보여줍니다.
class StudentRecord:
def __init__(self, name, test_scores, attendance, extra_credit=0):
self.name = name
self.test_scores = test_scores
self.attendance = attendance
self.extra_credit = extra_credit
@property
def average_score(self):
return sum(self.test_scores) / len(self.test_scores) if self.test_scores else 0
@property
def final_grade(self):
base = self.average_score * 0.8 + self.attendance * 0.2
return min(100, base + self.extra_credit)
def __repr__(self):
return f'{self.name}: {self.final_grade:.1f}'
students = [
StudentRecord('Alice', [85, 90, 88], 95, 5),
StudentRecord('Bob', [92, 88, 95], 80, 0),
StudentRecord('Charlie', [78, 82, 80], 100, 10),
StudentRecord('David', [90, 92, 88], 90, 2),
StudentRecord('Eve', [85, 85, 85], 95, 0)
]
# Rank by final grade (highest first), then name
rankings = sorted(students, key=lambda s: (-s.final_grade, s.name))
print("Student Rankings:")
for rank, student in enumerate(rankings, 1):
print(f"{rank}. {student}")실전 예시: 파일 목록 처리
파일 메타데이터 정렬은 다양한 데이터 타입과 커스텀 정렬 로직을 함께 다뤄야 합니다.
import os
from datetime import datetime
class FileInfo:
def __init__(self, path):
self.name = os.path.basename(path)
self.path = path
self.size = os.path.getsize(path)
self.modified = os.path.getmtime(path)
self.extension = os.path.splitext(path)[1].lower()
def __repr__(self):
size_kb = self.size / 1024
mod_time = datetime.fromtimestamp(self.modified).strftime('%Y-%m-%d %H:%M')
return f'{self.name} ({size_kb:.1f} KB, {mod_time})'
# Simulate file information
files = [
FileInfo('/data/report.pdf'),
FileInfo('/data/summary.txt'),
FileInfo('/data/analysis.xlsx'),
FileInfo('/data/backup.pdf'),
]
# Sort by extension, then size (largest first)
by_type_size = sorted(files, key=lambda f: (f.extension, -f.size))
# Sort by modification time (most recent first)
by_recent = sorted(files, key=lambda f: -f.modified)
# Sort by size, handling different units
def format_size(bytes_size):
if bytes_size < 1024:
return f"{bytes_size} B"
elif bytes_size < 1024 * 1024:
return f"{bytes_size/1024:.1f} KB"
else:
return f"{bytes_size/(1024*1024):.1f} MB"
by_size = sorted(files, key=lambda f: f.size, reverse=True)
for f in by_size:
print(f"{f.name}: {format_size(f.size)}")PyGWalker로 인터랙티브 데이터 정렬하기
대규모 데이터셋을 시각적으로 탐색하면서 인터랙티브하게 정렬해야 한다면, PyGWalker는 pandas DataFrame을 Tableau 스타일 인터페이스로 변환해줍니다. 복잡한 정렬 로직을 직접 작성하는 대신, 컬럼을 드래그 앤 드롭하여 시각적으로 정렬/필터링/분석할 수 있습니다.
import pandas as pd
import pygwalker as pyg
# Create sample sales data
sales_data = pd.DataFrame({
'product': ['Laptop', 'Phone', 'Tablet', 'Monitor', 'Keyboard'] * 100,
'region': ['North', 'South', 'East', 'West', 'Central'] * 100,
'revenue': [999, 599, 399, 299, 49] * 100,
'units_sold': [50, 120, 80, 95, 200] * 100,
'date': pd.date_range('2024-01-01', periods=500)
})
# Launch interactive visualization
pyg.walk(sales_data)PyGWalker로 가능한 작업:
- 시각적 피드백과 함께 여러 컬럼을 기준으로 정렬
- 정렬과 동적 필터링의 결합
- 코드를 쓰지 않고 계산 필드 기준으로 정렬
- 정렬된 뷰를 차트나 테이블로 내보내기
이 접근은 익숙하지 않은 데이터셋을 탐색하거나, Python 코드를 실행하지 않고도 데이터 패턴을 이해해야 하는 비기술 이해관계자에게 결과를 공유할 때 특히 유용합니다.
FAQ
Python에서 sorted()와 list.sort()의 차이는 무엇인가요?
sorted() 함수는 새로운 정렬된 리스트를 반환하며 어떤 iterable에도 동작하고, 원본을 변경하지 않습니다. list.sort() 메서드는 리스트를 제자리에서 수정하고 None을 반환하며 list 객체에서만 사용할 수 있습니다. 원본 데이터를 보존하거나 튜플 같은 non-list 시퀀스를 정렬하려면 sorted()를, 큰 리스트를 메모리 효율적으로 정렬하려면 list.sort()를 사용하세요.
Python에서 리스트를 내림차순으로 정렬하려면 어떻게 하나요?
sorted() 또는 list.sort()에 reverse=True를 추가하면 됩니다. 예: sorted([3, 1, 4], reverse=True)는 [4, 3, 1]을 반환합니다. 숫자, 문자열, 날짜 등 자연 순서를 가진 모든 데이터 타입에 적용됩니다.
Python에서 딕셔너리를 값(value) 기준으로 정렬할 수 있나요?
딕셔너리 자체는 전통적인 의미에서 순서가 있는 구조는 아니지만, sorted()와 key 함수를 사용해 items를 값 기준으로 정렬할 수 있습니다: sorted(dict.items(), key=lambda x: x[1]). 이는 값 기준으로 정렬된 튜플 리스트를 반환합니다. 필요하다면 dict(sorted(...))로 다시 딕셔너리로 변환할 수 있으며, 이때 순서 보존은 Python 3.7+에서만 보장됩니다.
특정 키 기준으로 딕셔너리 리스트를 정렬하려면 어떻게 하나요?
sorted()에 해당 키를 꺼내는 lambda를 key로 전달하세요: sorted(list_of_dicts, key=lambda x: x['keyname']). 성능을 더 원하면 operator.itemgetter: sorted(list_of_dicts, key=itemgetter('keyname'))를 사용할 수 있습니다. 두 방식 모두 단일/다중 키 정렬에 사용할 수 있습니다.
Python 정렬 함수의 시간 복잡도는 무엇인가요?
Python의 sorted()와 list.sort()는 Timsort 알고리즘을 사용하며, 최악의 경우 O(n log n) 시간 복잡도와 부분적으로 정렬된 데이터에 대해 최선의 경우 O(n) 복잡도를 가집니다. 공간 복잡도는 sorted()가 O(n)(새 리스트 생성), list.sort()가 O(1)(제자리)입니다. 큰 데이터셋에서 top k를 찾는 경우 heapq.nlargest()는 O(n log k)로 유리할 수 있습니다.
정렬할 때 None 값은 어떻게 처리하나요?
None이 다른 타입과 섞이면 비교 에러가 납니다. None을 앞이나 뒤로 보내는 key 함수를 사용하세요: sorted(data, key=lambda x: (x is None, x))는 None을 뒤로, sorted(data, key=lambda x: (x is not None, x))는 앞으로 보냅니다. 키가 없을 수 있는 딕셔너리는 x.get('key', default_value)로 기본값을 제공하세요.
Python 정렬은 안정적인가요?
네. Python 정렬은 안정 정렬이 보장되며, 동일한 key를 가진 원소들의 상대적 순서가 유지됩니다. 이 덕분에 한 기준으로 정렬한 뒤 다른 기준으로 다시 정렬하는 다단계(멀티패스) 정렬이 가능합니다. 이 안정성은 Python이 사용하는 Timsort 알고리즘에서 보장됩니다.
결론
Python의 정렬 기능은 데이터를 효율적으로 조직화할 수 있는 강력한 도구를 제공합니다. sorted()와 list.sort()의 차이를 이해하면 메모리를 고려한 결정을 내릴 수 있고, key 파라미터와 lambda 함수를 활용하면 정교한 커스텀 정렬 로직을 구현할 수 있습니다. 또한 안정 정렬, 다중 기준 정렬, 성능 특성을 이해하면 상황에 맞는 올바른 접근을 선택할 수 있습니다.
학생 성과 순위화, 파일 메타데이터 처리, 비즈니스 지표 분석 등 어떤 작업이든 이러한 정렬 기법은 데이터 조작 워크플로우의 기반이 됩니다. 시각적 데이터 탐색과 인터랙티브 정렬이 필요하다면, PyGWalker는 드래그 앤 드롭 인터페이스로 프로그램 방식 정렬을 보완해줍니다.
이 정렬 패턴들을 숙달하면 더 깔끔한 코드를 작성하고, 성능을 개선하며, 복잡한 데이터 정리 과제를 자신 있게 처리할 수 있습니다.