Сортировка списка по нескольким атрибутам?
У меня есть список списков:
[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]
Если я хочу отсортировать его по одному элементу, например, по элементу "tall/short", я могу сделать это с помощью функции s = sorted(s, key=itemgetter(1))
.
Однако если я хочу отсортировать по двум критериям - сначала по "tall/short", а затем по цвету, я мог бы отсортировать его дважды, по каждому элементу по отдельности. Но есть ли более быстрый способ сделать это?
5 ответ(ов)
Ключом для сортировки может быть функция, возвращающая кортеж:
s = sorted(s, key=lambda x: (x[1], x[2]))
Однако вы можете добиться того же результата с помощью itemgetter
, что будет быстрее и избавит от вызова функции на Python:
import operator
s = sorted(s, key=operator.itemgetter(1, 2))
Также обратите внимание, что в данном случае вы можете использовать метод sort
вместо sorted
, чтобы избежать повторного присваивания:
s.sort(key=operator.itemgetter(1, 2))
Вы указываете, что хотите отсортировать список кортежей сначала по убыванию целочисленных значений, а затем в алфавитном порядке. Ваше решение действительно работает, но с точки зрения питоновости его можно немного улучшить.
Вместо того чтобы использовать два вызова функции sorted
, можно применить один вызов с кортежем в качестве ключа сортировки. Это позволит вам задать оба условия сортировки в одном месте, что сделает код более читаемым. Вот как это может выглядеть:
a = [('Al', 2), ('Bill', 1), ('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]
b = sorted(a, key=lambda x: (-x[1], x[0]))
print(b)
В этом примере мы используем кортеж (-x[1], x[0])
в качестве ключа для сортировки. Знак минус перед x[1]
обеспечивает сортировку по убыванию для целочисленных значений, а x[0]
сортирует строки в алфавитном порядке.
Результат выполнения этого кода будет прежним:
[('Abel', 3), ('Al', 2), ('Carol', 2), ('Zeke', 2), ('Bill', 1), ('Chris', 1)]
Таким образом, этот метод более питоновый и, вероятно, будет более понятен другим разработчикам.
Несколько лет спустя, но я хочу сортировать по двум критериям и использовать reverse=True
. Если вдруг кому-то это будет интересно, вы можете обернуть свои критерии (функции) в скобки:
s = sorted(my_list, key=lambda i: (criteria_1(i), criteria_2(i)), reverse=True)
Похоже, вместо tuple
вы можете использовать list
. Это становится особенно важным, когда вы работаете с атрибутами, а не с "магическими индексами" списка или кортежа.
В моем случае мне нужно было сортировать по нескольким атрибутам класса, где входные ключи были строками. Я хотел иметь разную сортировку в разных местах и общую сортировку по умолчанию для родительского класса, с которым взаимодействовали клиенты; при этом мне нужно было переопределять "ключи сортировки", только когда это действительно необходимо, но также так, чтобы я мог хранить их в виде списков, которыми мог бы делиться класс.
Сначала я определил вспомогательный метод:
def attr_sort(self, attrs=['someAttributeString']):
'''вспомогательный метод для сортировки по атрибутам, названным строками в attrs в указанном порядке'''
return lambda k: [getattr(k, attr) for attr in attrs]
Затем, чтобы использовать его:
# будет определено в другом месте, но показываю здесь для краткости
self.SortListA = ['attrA', 'attrB']
self.SortListB = ['attrC', 'attrA']
records = .... # список моих объектов для сортировки
records.sort(key=self.attr_sort(attrs=self.SortListA))
# возможно, позже, рядом или в другой функции
more_records = .... # другой список
more_records.sort(key=self.attr_sort(attrs=self.SortListB))
Это позволит вам использовать сгенерированную лямбда-функцию для сортировки списка по object.attrA
, затем по object.attrB
, если object
имеет соответствующий геттер для предоставленных строковых имен. Во втором случае сортировка будет выполнена по object.attrC
, затем по object.attrA
.
Такой подход также позволяет потенциально предоставить возможность выбора параметров сортировки, которые могут быть разделены потребителем, юнит-тестом или, возможно, позволяет им указать, как они хотят, чтобы сортировка была выполнена для какой-то операции в вашем API, предоставив лишь список, а не связывая их с реализацией вашего бекенда.
Чтобы преобразовать список списков в список кортежей и отсортировать кортежи по нескольким полям, вы можете использовать такой подход:
data = [[12, 'tall', 'blue', 1], [2, 'short', 'red', 9], [4, 'tall', 'blue', 13]]
# Преобразуем каждый вложенный список в кортеж
data = [tuple(x) for x in data]
# Сортируем кортежи по нескольким полям: сначала по второму элементу (рост), затем по третьему (цвет)
result = sorted(data, key=lambda x: (x[1], x[2]))
print(result)
Выходной результат будет следующим:
[(2, 'short', 'red', 9), (12, 'tall', 'blue', 1), (4, 'tall', 'blue', 13)]
Таким образом, вы сначала комбинируете элементы из вложенных списков в кортежи, а затем сортируете их по указанным полям с помощью функции sorted
и лямбда-функции для задания ключа сортировки.
Как отсортировать список словарей по значению словаря в Python?
Как изменить порядок столбцов в DataFrame?
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе
Как отсортировать DataFrame pandas по одному столбцу
Сортировка списка в соответствии с соответствующими значениями из параллельного списка