Сортировка списка по нескольким атрибутам?

У меня есть список списков:

[[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)