Многоуровневая сортировка в Python: техники для сложных данных
Для кого эта статья:
- Разработчики, изучающие Python и его возможности работы со структурами данных
- Студенты и начинающие программисты, стремящиеся улучшить свои навыки в сортиовании данных
Эксперты и практикующие программисты, заинтересованные в оптимизации кода и повышении производительности при работе с большими объемами данных
Структуры данных в Python регулярно требуют не просто "расставить элементы по порядку", а создать сложную иерархию приоритетов. Когда ваш код сортирует сотрудников сначала по отделу, затем по стажу, а потом по фамилии – вы погружаетесь в мир многоуровневой сортировки. Эффективное владение этими методами отличает рядового кодера от Python-мастера. Случайная сортировка способна превратить рабочий код в источник неуловимых багов, а точное управление сортировкой – критически важный навык для работы с данными любой сложности 🧩.
Хотите превратить сырые знания о сортировке в Python в профессиональный инструментарий разработчика? На курсе Обучение Python-разработке от Skypro вы не только освоите многоуровневую сортировку данных, но и научитесь применять эти техники в реальных проектах под руководством практикующих экспертов. От базовых алгоритмов до продвинутой оптимизации кода – погружение в Python-разработку с нуля до уровня, востребованного на рынке.
Основы многоуровневой сортировки в Python: принципы работы
Многоуровневая сортировка в Python – это техника упорядочивания элементов коллекции по нескольким критериям с установленным приоритетом. Когда значения первичного ключа совпадают, в игру вступает вторичный критерий, затем третичный и так далее. Это фундаментальный паттерн работы с данными, который используется повсеместно – от сортировки таблиц до построения сложных отчётов.
Понимание стабильности сортировки – ключевой момент для многоуровневой сортировки. Python гарантирует, что встроенные алгоритмы сортировки сохраняют относительный порядок элементов с одинаковыми значениями ключа. Без этой гарантии последовательное применение однокритериальных сортировок не давало бы желаемого результата.
Алексей Демидов, ведущий разработчик Python
Однажды нам поступила задача отображать в интерфейсе список заказов, отсортированный одновременно по трём параметрам: статусу (сначала активные, потом завершённые), приоритету (от высокого к низкому) и времени создания (от новых к старым). Мы попытались решить задачу последовательной сортировкой: сначала по времени, потом по приоритету, в конце по статусу. Результат был неправильным – последовательность сортировки имеет значение! Правильный порядок обратный: сначала по наименее значимому критерию (времени), затем по более важному (приоритету), и наконец по самому важному (статусу). Стабильность алгоритма сортировки гарантирует, что предыдущий порядок не нарушится при следующей сортировке, если значения равны.
Механизм сравнения в Python опирается на несколько ключевых принципов:
- При сортировке элементов Python применяет операторы сравнения (<, >, ==)
- Встроенные типы имеют предопределённые правила сравнения
- Пользовательские объекты могут определять своё поведение при сравнении через магические методы (lt, gt и др.)
- Функция ключа (key) преобразует элементы в сопоставимые значения
Рассмотрим простейший пример многоуровневой сортировки с использованием кортежей:
# Список студентов: (имя, курс, средний балл)
students = [
("Алексей", 2, 4.5),
("Мария", 1, 4.8),
("Иван", 2, 4.2),
("Елена", 1, 4.8)
]
# Сортировка по курсу (по возрастанию), затем по среднему баллу (по убыванию)
sorted_students = sorted(students, key=lambda x: (x[1], -x[2]))
print(sorted_students)
# Результат: [('Елена', 1, 4.8), ('Мария', 1, 4.8), ('Иван', 2, 4.2), ('Алексей', 2, 4.5)]
Принципиальное отличие многоуровневой сортировки от последовательных вызовов состоит в эффективности: один проход через данные вместо нескольких. А это критически важно при работе с большими объёмами информации.
| Характеристика | Одиночная сортировка | Последовательные сортировки | Многоуровневая сортировка |
|---|---|---|---|
| Временная сложность | O(n log n) | O(k * n log n) где k – число критериев | O(n log n) |
| Количество проходов по данным | 1 | k (число критериев) | 1 |
| Простота реализации | Высокая | Средняя | Средняя |
| Гибкость направления сортировки | Низкая | Высокая | Высокая |

Сортировка с использованием lambda функций: составные ключи
Lambda-функции – мощный инструмент для создания составных ключей сортировки, позволяющий лаконично описать логику упорядочивания. Они особенно полезны, когда требуется нестандартное преобразование или комбинация значений.
Основная идея использования lambda с составными ключами – возвращать кортеж значений, по которым будет проводиться сравнение:
# Каталог товаров: название, категория, цена, рейтинг
products = [
{"name": "Ноутбук", "category": "Электроника", "price": 45000, "rating": 4.8},
{"name": "Смартфон", "category": "Электроника", "price": 20000, "rating": 4.5},
{"name": "Кофемашина", "category": "Бытовая техника", "price": 15000, "rating": 4.7},
{"name": "Наушники", "category": "Электроника", "price": 5000, "rating": 4.9},
]
# Сортировка по категории, затем по убыванию рейтинга, затем по возрастанию цены
sorted_products = sorted(products, key=lambda x: (
x["category"], # первичный ключ – категория (лексикографически)
-x["rating"], # вторичный ключ – рейтинг (по убыванию)
x["price"] # третичный ключ – цена (по возрастанию)
))
for p in sorted_products:
print(f"{p['name']} – {p['category']}, рейтинг: {p['rating']}, цена: {p['price']}")
Обратите внимание на минус перед x["rating"] – это типичный приём для сортировки по убыванию в составном ключе. Однако этот подход работает только с числовыми значениями. Для строк и других типов потребуется другое решение.
Когда требуется комбинировать разные направления сортировки для разных типов данных, можно использовать более сложный подход:
from functools import cmp_to_key
# Сортировка с разными направлениями для разных типов данных
def custom_compare(a, b):
# Сначала по категории (по возрастанию)
if a["category"] != b["category"]:
return -1 if a["category"] < b["category"] else 1
# Затем по рейтингу (по убыванию)
if a["rating"] != b["rating"]:
return -1 if a["rating"] > b["rating"] else 1
# И наконец по цене (по возрастанию)
return -1 if a["price"] < b["price"] else 1
# Применение пользовательской функции сравнения
sorted_products = sorted(products, key=cmp_to_key(custom_compare))
Однако такой подход менее эффективен, и его следует использовать только при необходимости сложной логики сравнения, которую трудно выразить составным ключом.
Часто требуется сортировать по вычисляемым значениям или применять функции преобразования. Lambda-функции справляются и с этим:
# Сортировка по длине имени, затем по алфавиту
names = ["Александр", "Иван", "Мария", "Анна", "Екатерина"]
sorted_names = sorted(names, key=lambda x: (len(x), x))
print(sorted_names)
# Результат: ['Анна', 'Иван', 'Мария', 'Александр', 'Екатерина']
# Сортировка словарей с игнорированием регистра
words = [{"word": "Apple"}, {"word": "banana"}, {"word": "Cherry"}, {"word": "date"}]
sorted_words = sorted(words, key=lambda x: x["word"].lower())
print([w["word"] for w in sorted_words])
# Результат: ['Apple', 'banana', 'Cherry', 'date']
Еще одно преимущество lambda-функций – возможность использовать условную логику прямо в ключе сортировки:
# Данные сотрудников
employees = [
{"name": "Алексей", "department": "Разработка", "is_manager": False, "salary": 120000},
{"name": "Ирина", "department": "Маркетинг", "is_manager": True, "salary": 150000},
{"name": "Сергей", "department": "Разработка", "is_manager": True, "salary": 180000},
{"name": "Ольга", "department": "Маркетинг", "is_manager": False, "salary": 90000},
]
# Сортировка: сначала руководители, затем по отделам, потом по зарплате (по убыванию)
sorted_employees = sorted(employees, key=lambda x: (
0 if x["is_manager"] else 1, # Сначала руководители
x["department"], # Затем по отделу
-x["salary"] # Потом по зарплате (по убыванию)
))
for e in sorted_employees:
print(f"{e['name']} – {e['department']}, " +
f"{'Руководитель' if e['is_manager'] else 'Сотрудник'}, " +
f"зарплата: {e['salary']}")
| Приём сортировки | Пример использования | Плюсы | Минусы |
|---|---|---|---|
| Составной кортеж | lambda x: (x[0], x[1]) | Компактность, производительность | Ограниченная гибкость с направлением |
| Инверсия числового значения | lambda x: (x[0], -x[1]) | Простая реализация убывания | Работает только с числами |
| Условный ключ | lambda x: (0 if x.is_active else 1, x.name) | Гибкость в приоритизации | Может усложнить чтение кода |
Функция cmp_to_key | cmp_to_key(custom_compare) | Максимальная гибкость | Снижение производительности, многословность |
Метод sorted() и sort() для многокритериальной сортировки
Python предоставляет два основных метода для сортировки: функцию sorted() и метод списков .sort(). Оба поддерживают многокритериальную сортировку, но имеют существенные различия, о которых нужно помнить при выборе подходящего инструмента.
Ключевые различия между sorted() и sort():
- sorted() – функция, создающая новый отсортированный список, не изменяя исходный
- list.sort() – метод, который сортирует список на месте, изменяя исходный объект
- sorted() может принимать любой итерируемый объект, включая кортежи, словари, строки
- list.sort() доступен только для списков
- sorted() возвращает новый список, что позволяет использовать его в выражениях
- list.sort() возвращает None, что предотвращает цепочку вызовов
Рассмотрим практические примеры многокритериальной сортировки с использованием обоих методов:
# Список студентов с разными атрибутами
students = [
{"name": "Алексей", "group": "А", "grade": 85, "attendance": 90},
{"name": "Мария", "group": "Б", "grade": 92, "attendance": 95},
{"name": "Дмитрий", "group": "А", "grade": 78, "attendance": 85},
{"name": "Светлана", "group": "Б", "grade": 92, "attendance": 88},
{"name": "Иван", "group": "А", "grade": 85, "attendance": 92},
]
# Использование sorted() – сортировка по группе, затем по оценке (по убыванию)
sorted_students = sorted(
students,
key=lambda s: (s["group"], -s["grade"], -s["attendance"])
)
print("Результат sorted():")
for s in sorted_students:
print(f"{s['name']}: группа {s['group']}, оценка {s['grade']}, посещаемость {s['attendance']}%")
# Использование sort() – модифицирует исходный список
students_copy = students.copy() # создаём копию, чтобы не изменить оригинал
students_copy.sort(key=lambda s: (s["group"], -s["grade"], -s["attendance"]))
print("\nРезультат sort():")
for s in students_copy:
print(f"{s['name']}: группа {s['group']}, оценка {s['grade']}, посещаемость {s['attendance']}%")
При работе с обоими методами можно использовать дополнительный параметр reverse=True для общего обращения порядка сортировки. Однако это влияет на все критерии сортировки одновременно, что не всегда желательно при многоуровневой сортировке.
Максим Ковалёв, архитектор данных
При работе с финансовой платформой мы столкнулись с интересным кейсом. Нам нужно было отображать транзакции пользователей, сгруппированные по типу операции, внутри каждой группы – отсортированные по дате (новые сверху), а при одинаковых датах – по сумме (от большей к меньшей). В первой итерации кода мы использовали три последовательных вызова
sort(), для каждого критерия отдельно. Система работала, но на больших объёмах данных скорость была неприемлемой.После рефакторинга мы перешли на многокритериальную сортировку с составным ключом:
PythonСкопировать кодtransactions.sort(key=lambda t: ( t['operation_type'], -int(t['date'].timestamp()), # Отрицательный timestamp для сортировки по убыванию -t['amount'] ))Производительность выросла в разы, особенно заметно на выборках по 10000+ транзакций. Составные ключи сортировки – настоящая находка для подобных задач!
При работе со сложными объектами часто требуется извлечение или преобразование атрибутов. Функциональный стиль Python позволяет делать это элегантно:
# Класс для представления книги
class Book:
def __init__(self, title, author, year, rating):
self.title = title
self.author = author
self.year = year
self.rating = rating
def __repr__(self):
return f"{self.title} ({self.year}) by {self.author}, рейтинг: {self.rating}"
# Создание списка книг
books = [
Book("Война и мир", "Толстой Л.Н.", 1869, 4.9),
Book("Преступление и наказание", "Достоевский Ф.М.", 1866, 4.8),
Book("Мастер и Маргарита", "Булгаков М.А.", 1967, 4.9),
Book("Анна Каренина", "Толстой Л.Н.", 1877, 4.7),
]
# Сортировка по автору, году публикации и рейтингу (по убыванию)
sorted_books = sorted(books, key=lambda book: (book.author, book.year, -book.rating))
print("Отсортированный список книг:")
for book in sorted_books:
print(book)
Для более сложных сценариев можно комбинировать методы сортировки с группировкой и другими операциями над данными:
# Группировка и сортировка внутри групп
from itertools import groupby
# Сначала отсортируем по автору для правильной группировки
books.sort(key=lambda book: book.author)
# Группировка по автору и сортировка внутри групп по году (по возрастанию)
for author, books_by_author in groupby(books, key=lambda book: book.author):
print(f"\nКниги автора {author}:")
# Преобразуем итератор в список и отсортируем по году
author_books = sorted(list(books_by_author), key=lambda book: book.year)
for book in author_books:
print(f" {book.title} ({book.year}), рейтинг: {book.rating}")
Применение itemgetter и attrgetter для эффективной сортировки
Использование lambda-функций для сортировки интуитивно понятно, но для повышения производительности Python предлагает специализированные инструменты из модуля operator: itemgetter и attrgetter. Эти функции оптимизированы для извлечения значений из объектов и работают быстрее, чем эквивалентные lambda-выражения.
itemgetter идеально подходит для сортировки по ключам словарей или индексам последовательностей:
from operator import itemgetter
# Список словарей с информацией о продуктах
products = [
{"id": 1, "name": "Ноутбук", "price": 45000, "stock": 12},
{"id": 2, "name": "Смартфон", "price": 20000, "stock": 45},
{"id": 3, "name": "Планшет", "price": 30000, "stock": 8},
{"id": 4, "name": "Монитор", "price": 15000, "stock": 24},
]
# Сортировка с использованием itemgetter (по наличию, затем по цене)
sorted_by_stock_price = sorted(
products,
key=itemgetter("stock", "price")
)
print("Сортировка по запасам и цене:")
for product in sorted_by_stock_price:
print(f"{product['name']}: {product['stock']} шт., {product['price']} ₽")
# Сортировка по нескольким критериям с разным направлением
# (сначала по убыванию запасов, затем по возрастанию цены)
from functools import cmp_to_key
def custom_compare(a, b):
# По запасам (по убыванию)
if a["stock"] != b["stock"]:
return -1 if a["stock"] > b["stock"] else 1
# По цене (по возрастанию)
if a["price"] != b["price"]:
return -1 if a["price"] < b["price"] else 1
return 0
sorted_custom = sorted(products, key=cmp_to_key(custom_compare))
print("\nСложная сортировка (запасы по убыванию, цена по возрастанию):")
for product in sorted_custom:
print(f"{product['name']}: {product['stock']} шт., {product['price']} ₽")
Для работы с объектами классов оптимальным решением является использование attrgetter, который извлекает значения атрибутов объектов:
from operator import attrgetter
class Employee:
def __init__(self, name, department, salary, experience):
self.name = name
self.department = department
self.salary = salary
self.experience = experience
def __repr__(self):
return f"{self.name} ({self.department}): {self.salary}₽, опыт: {self.experience} лет"
# Список сотрудников
employees = [
Employee("Иванов А.С.", "Разработка", 120000, 5),
Employee("Петров И.И.", "Маркетинг", 90000, 3),
Employee("Сидорова О.П.", "Разработка", 150000, 7),
Employee("Кузнецова Е.В.", "Маркетинг", 100000, 5),
Employee("Смирнов Д.А.", "Разработка", 130000, 4),
]
# Сортировка по отделу и опыту
sorted_by_dept_exp = sorted(
employees,
key=attrgetter("department", "experience")
)
print("Сортировка по отделу и опыту:")
for employee in sorted_by_dept_exp:
print(employee)
# Сортировка по нескольким критериям с разным направлением
# (отдел, затем по убыванию зарплаты)
from operator import attrgetter
import functools
def negate_numeric(obj, attr):
"""Возвращает отрицательное значение числового атрибута"""
return -getattr(obj, attr)
# Создаем составную функцию ключа
def composite_key(obj):
return (obj.department, negate_numeric(obj, "salary"))
sorted_employees = sorted(employees, key=composite_key)
print("\nСортировка по отделу и зарплате (по убыванию):")
for employee in sorted_employees:
print(employee)
Сравнение производительности различных методов сортировки на больших данных показывает преимущество специализированных функций:
| Метод | Время выполнения на 100,000 элементах | Память | Читаемость кода |
|---|---|---|---|
| Lambda | 1.2x базовое время | Базовое потребление | Высокая |
| itemgetter | 0.9x базовое время | 0.95x базовое потребление | Средняя |
| attrgetter | 0.85x базовое время | 0.95x базовое потребление | Средняя |
| cmptokey | 1.7x базовое время | 1.1x базовое потребление | Низкая |
Преимущества использования itemgetter и attrgetter:
- Улучшенная производительность по сравнению с lambda-функциями
- Лаконичный синтаксис при извлечении нескольких полей
- Чёткое выражение намерений в коде (ясно, что происходит извлечение атрибутов)
- Возможность повторного использования созданных геттеров
Для комбинирования разных направлений сортировки с itemgetter или attrgetter можно использовать специализированные обертки:
from operator import itemgetter
# Вспомогательная функция для сортировки по убыванию
def desc_itemgetter(*items):
getter = itemgetter(*items)
return lambda obj: tuple(-x if isinstance(x, (int, float)) else x for x in getter(obj))
# Сортировка списка словарей по полю "stock" по убыванию, затем "price" по возрастанию
products = [
{"name": "Ноутбук", "price": 45000, "stock": 12},
{"name": "Смартфон", "price": 20000, "stock": 12},
{"name": "Планшет", "price": 30000, "stock": 8},
]
# Сначала сортируем по price (для случаев, когда stock одинаковый)
products.sort(key=itemgetter("price"))
# Затем сортируем по stock по убыванию
products.sort(key=lambda x: -x["stock"])
print("Результат сортировки:")
for p in products:
print(f"{p['name']}: {p['stock']} шт., {p['price']} ₽")
Сортировка объектов по нескольким атрибутам: практические кейсы
Многоуровневая сортировка – не просто теоретический концепт, а практический инструмент, решающий реальные проблемы разработки. Рассмотрим несколько распространенных сценариев, где этот подход демонстрирует свою ценность. 🔍
Наиболее частые задачи, требующие сортировки по нескольким атрибутам:
- Отображение данных в пользовательском интерфейсе (таблицы, списки)
- Формирование отчетов с группировкой данных
- Поиск дубликатов и близких значений
- Анализ трендов и отклонений
- Организация данных для оптимизации доступа
Рассмотрим практический кейс: интернет-магазин, где товары нужно отображать по определенным критериям:
from dataclasses import dataclass
from datetime import datetime, timedelta
from operator import attrgetter
import random
@dataclass
class Product:
id: int
name: str
category: str
price: float
rating: float
stock: int
is_premium: bool
added_date: datetime
def __repr__(self):
return (
f"{self.name} ({self.category}): "
f"${self.price:.2f}, ⭐{self.rating:.1f}, "
f"{'🔶 Premium' if self.is_premium else ''},"
f" {self.stock} шт."
)
# Создание тестовых данных
categories = ["Электроника", "Одежда", "Книги", "Дом и сад"]
names = [
"Смартфон X10", "Ноутбук Pro", "Футболка классическая",
"Джинсы универсальные", "Роман 'Война и мир'", "Учебник Python",
"Садовый шланг", "Набор инструментов"
]
# Генерация случайных товаров
products = []
for i in range(20):
category = random.choice(categories)
name = random.choice(names)
premium = random.choice([True, False])
products.append(Product(
id=i+1,
name=f"{name} {i+1}",
category=category,
price=round(random.uniform(10, 1000), 2),
rating=round(random.uniform(3.0, 5.0), 1),
stock=random.randint(0, 50),
is_premium=premium,
added_date=datetime.now() – timedelta(days=random.randint(0, 365))
))
# Задача 1: Показать товары на главной странице
# – Сначала товары в наличии (stock > 0)
# – Затем премиум-товары выше обычных
# – В рамках премиальности, сортировка по рейтингу (по убыванию)
# – При равном рейтинге, сортировка по цене (по возрастанию)
def main_page_key(product):
return (
0 if product.stock > 0 else 1, # Товары в наличии выше
0 if product.is_premium else 1, # Премиум товары выше
-product.rating, # Высокий рейтинг выше
product.price # Низкая цена выше при равном рейтинге
)
main_page_products = sorted(products, key=main_page_key)
print("Товары для главной страницы:")
for i, product in enumerate(main_page_products[:10], 1): # Показываем топ-10
print(f"{i}. {product}")
# Задача 2: Товары для категориальной страницы
# – Сгруппированы по категории
# – Внутри категории по новизне (новые выше)
# – При равной дате добавления – по рейтингу (по убыванию)
def category_page_key(product):
return (
product.category,
-int(product.added_date.timestamp()), # Отрицательный timestamp для сортировки по убыванию
-product.rating
)
category_page_products = sorted(products, key=category_page_key)
print("\nТовары для категориальных страниц:")
current_category = None
for product in category_page_products:
if product.category != current_category:
current_category = product.category
print(f"\n--- {current_category} ---")
print(f"{product.name}: рейтинг {product.rating}, добавлен {product.added_date.strftime('%d.%m.%Y')}")
Усложним задачу: рассмотрим сортировку связанных объектов в сложной структуре данных:
from collections import defaultdict
@dataclass
class Order:
id: int
customer_id: int
date: datetime
total: float
status: str
def __repr__(self):
return f"Заказ #{self.id}: ${self.total:.2f}, {self.date.strftime('%d.%m.%Y')}, {self.status}"
# Создание тестовых заказов
statuses = ["Новый", "Обработка", "Отправлен", "Доставлен", "Отменен"]
orders = []
for i in range(50):
customer_id = random.randint(1, 10)
date = datetime.now() – timedelta(days=random.randint(0, 90))
orders.append(Order(
id=i+1,
customer_id=customer_id,
date=date,
total=round(random.uniform(10, 500), 2),
status=random.choice(statuses)
))
# Группировка заказов по клиентам
customer_orders = defaultdict(list)
for order in orders:
customer_orders[order.customer_id].append(order)
# Сортировка заказов для каждого клиента
# – Сначала по статусу (в порядке важности: Новый, Обработка, Отправлен, Доставлен, Отменен)
# – Затем по дате (новые выше)
# – При равной дате по сумме (большие выше)
status_priority = {
"Новый": 0,
"Обработка": 1,
"Отправлен": 2,
"Доставлен": 3,
"Отменен": 4
}
def order_sort_key(order):
return (
status_priority[order.status],
-int(order.date.timestamp()),
-order.total
)
# Сортировка заказов для каждого клиента
for customer_id, orders in customer_orders.items():
customer_orders[customer_id] = sorted(orders, key=order_sort_key)
# Показ отсортированных заказов
print("\nЗаказы по клиентам:")
for customer_id in sorted(customer_orders.keys())[:3]: # Показываем только первых 3 клиентов
print(f"\nКлиент #{customer_id}:")
for order in customer_orders[customer_id]:
print(f" {order}")
Одна из интересных задач – эффективная сортировка разнородных объектов, где критерии могут быть вычисляемыми или зависеть от сложной бизнес-логики:
@dataclass
class Content:
id: int
title: str
type: str # "article", "video", "podcast"
publish_date: datetime
views: int
likes: int
duration: int = 0 # в секундах для видео/подкастов
@property
def engagement_rate(self):
"""Вычисляемый показатель вовлеченности"""
if self.views == 0:
return 0
return (self.likes / self.views) * 100
def __repr__(self):
type_emoji = {"article": "📄", "video": "🎬", "podcast": "🎙️"}
return (
f"{type_emoji.get(self.type, '❓')} {self.title} – "
f"👁️ {self.views}, ❤️ {self.likes}, "
f"ER: {self.engagement_rate:.1f}%"
)
# Создаем тестовый контент
content_items = []
for i in range(30):
content_type = random.choice(["article", "video", "podcast"])
views = random.randint(100, 10000)
likes = random.randint(0, min(views, 2000))
duration = 0 if content_type == "article" else random.randint(60, 3600)
content_items.append(Content(
id=i+1,
title=f"Контент #{i+1}",
type=content_type,
publish_date=datetime.now() – timedelta(days=random.randint(0, 30)),
views=views,
likes=likes,
duration=duration
))
# Задача: формирование персонализированной ленты контента
# Критерии персонализации:
# 1. Популярность (ER) – высокий ER выше
# 2. Свежесть – новые выше
# 3. Приоритет типов: видео > статьи > подкасты
# 4. Для видео – более короткие выше
type_priority = {"video": 0, "article": 1, "podcast": 2}
def personalized_feed_key(item):
# Нормализуем ER для сравнения (инверсия для сортировки по убыванию)
normalized_er = -item.engagement_rate
# Нормализуем дату (инверсия для сортировки по убыванию)
date_factor = -int(item.publish_date.timestamp())
# Фактор типа контента
type_factor = type_priority[item.type]
# Фактор длительности (только для видео и подкастов)
duration_factor = item.duration if item.type != "article" else 0
return (normalized_er, date_factor, type_factor, duration_factor)
# Сортировка контента для персонализированной ленты
personalized_feed = sorted(content_items, key=personalized_feed_key)
print("\nПерсонализированная лента контента:")
for i, item in enumerate(personalized_feed[:10], 1):
print(f"{i}. {item}")
Многоуровневая сортировка в Python — мощный инструмент, позволяющий элегантно решать сложные задачи упорядочивания данных. От простой сортировки списков до создания персонализированных лент контента — эти техники применимы практически в любом проекте. Помните, что выбор между lambda, itemgetter и attrgetter стоит делать осознанно, исходя из конкретных требований к производительности и читаемости кода. Составные ключи, манипуляции с направлением сортировки и группировка данных — это тот базовый арсенал, который превращает обычного кодера в мастера обработки данных.