Сортировка списка по нескольким атрибутам?
У меня есть список списков:
[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]
Если бы я хотел сортировать по одному элементу, например, высокий/короткий элемент, я мог бы сделать это через s = sorted(s, key = itemgetter(1))
.
Если бы я хотел сортировать как высокий/короткий, так и цвет, я мог бы сделать сортировку дважды, один раз для каждого элемента, но есть ли более быстрый способ?
Ответы
Ответ 1
Ключ может быть функцией, которая возвращает кортеж:
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))
Ответ 2
Я не уверен, что это самый пифонический метод...
У меня был список кортежей, которые нуждались в сортировке 1-го по нисходящим целым значениям и 2-м в алфавитном порядке. Это потребовало изменения целочисленной сортировки, но не алфавитной сортировки. Вот мое решение: (на лету на экзамене btw, я даже не знал, что вы можете "раскладывать" свои функции)
a = [('Al', 2),('Bill', 1),('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]
b = sorted(sorted(a, key = lambda x : x[0]), key = lambda x : x[1], reverse = True)
print(b)
[('Abel', 3), ('Al', 2), ('Carol', 2), ('Zeke', 2), ('Bill', 1), ('Chris', 1)]
Ответ 3
Похоже, вы можете использовать list
вместо tuple
.
Это становится более важным, я думаю, когда вы захватываете атрибуты вместо "волшебных индексов" списка/кортежа.
В моем случае я хотел сортировать по нескольким атрибутам класса, где входящие ключи были строками. Мне нужна была другая сортировка в разных местах, и мне нужен общий тип сортировки для родительского класса, с которым клиенты взаимодействовали; только для того, чтобы переопределить "сортировочные ключи", когда мне действительно нужно ", но также и так, чтобы я мог хранить их в виде списков, которые класс мог бы делиться
Итак, сначала я определил вспомогательный метод
def attr_sort(self, attrs=['someAttributeString']:
'''helper to sort by the attributes named by strings of attrs in order'''
return lambda k: [ getattr(k, attr) for attr in attrs ]
затем использовать его
# would defined elsewhere but showing here for consiseness
self.SortListA = ['attrA', 'attrB']
self.SortListB = ['attrC', 'attrA']
records = .... #list of my objects to sort
records.sort(key=self.attr_sort(attrs=self.SortListA))
# perhaps later nearby or in another function
more_records = .... #another list
more_records.sort(key=self.attr_sort(attrs=self.SortListB))
Это будет использовать сгенерированную функцию лямбды, сортирующую список по object.attrA
, а затем object.attrB
, предполагая, что object
имеет геттер, соответствующий указанным именам строк. А второй случай будет сортироваться по object.attrC
, затем object.attrA
.
Это также позволяет вам потенциально подвергать внешние сортировки предпочтениям, похожим на осколки, потребителем, unit test или для них, возможно, рассказать вам, как они хотят, чтобы сортировка, выполненная для какой-либо операции в вашем api, должна была дать вам список и не связывать их с реализацией на задней панели.
Ответ 4
Здесь один способ: вы в основном переписываете свою функцию сортировки, чтобы получить список функций сортировки, каждая функция сортировки сравнивает атрибуты, которые вы хотите протестировать, при каждом тесте сортировки вы смотрите и видите, возвращает ли функция cmp ненулевой возврат если так, сломайте и отправьте возвращаемое значение. Вы вызываете это, вызывая лямбду-функцию из списка лямбда-выражений.
Его преимущество заключается в том, что он выполняет однократный проход через данные, а не в виде предыдущего вида, как это делают другие методы. Другое дело, что сортировка происходит на месте, тогда как сортировка, похоже, делает копию.
Я использовал его для написания функции ранжирования, которая ранжирует список классов, где каждый объект находится в группе и имеет функцию оценки, но вы можете добавить любой список атрибутов. Обратите внимание на не лямбда-подобное, хотя и хакерское использование лямбды для вызова сеттера. Часть ранга не будет работать для массива списков, но сортировка будет.
#First, here a pure list version
my_sortLambdaLst = [lambda x,y:cmp(x[0], y[0]), lambda x,y:cmp(x[1], y[1])]
def multi_attribute_sort(x,y):
r = 0
for l in my_sortLambdaLst:
r = l(x,y)
if r!=0: return r #keep looping till you see a difference
return r
Lst = [(4, 2.0), (4, 0.01), (4, 0.9), (4, 0.999),(4, 0.2), (1, 2.0), (1, 0.01), (1, 0.9), (1, 0.999), (1, 0.2) ]
Lst.sort(lambda x,y:multi_attribute_sort(x,y)) #The Lambda of the Lambda
for rec in Lst: print str(rec)
Здесь способ ранжировать список объектов
class probe:
def __init__(self, group, score):
self.group = group
self.score = score
self.rank =-1
def set_rank(self, r):
self.rank = r
def __str__(self):
return '\t'.join([str(self.group), str(self.score), str(self.rank)])
def RankLst(inLst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank)):
#Inner function is the only way (I could think of) to pass the sortLambdaLst into a sort function
def multi_attribute_sort(x,y):
r = 0
for l in sortLambdaLst:
r = l(x,y)
if r!=0: return r #keep looping till you see a difference
return r
inLst.sort(lambda x,y:multi_attribute_sort(x,y))
#Now Rank your probes
rank = 0
last_group = group_lambda(inLst[0])
for i in range(len(inLst)):
rec = inLst[i]
group = group_lambda(rec)
if last_group == group:
rank+=1
else:
rank=1
last_group = group
SetRank_Lambda(inLst[i], rank) #This is pure evil!! The lambda purists are gnashing their teeth
Lst = [probe(4, 2.0), probe(4, 0.01), probe(4, 0.9), probe(4, 0.999), probe(4, 0.2), probe(1, 2.0), probe(1, 0.01), probe(1, 0.9), probe(1, 0.999), probe(1, 0.2) ]
RankLst(Lst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank))
print '\t'.join(['group', 'score', 'rank'])
for r in Lst: print r
Ответ 5
Несколько лет опоздал на вечеринку, но я хочу как отсортировать по 2 критериям, так и использовать reverse=True
. Если кто-то хочет знать, как, вы можете заключить свои критерии (функции) в круглые скобки:
s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)