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


457

У мене є список списків:

[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]

Якби я хотів сортувати за одним елементом, скажімо, високий / короткий елемент, я міг би зробити це через s = sorted(s, key = itemgetter(1)).

Якби я хотів , щоб сортувати і високий / короткі і колір, я міг би зробити вигляд двічі, один раз для кожного елемента, але є більш швидкий спосіб?



8
Якщо ви використовуєте кортежі замість списків, під час запуску python впорядковує сортування за записами зліва направо sort. Тобто sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)].
Mateen Ulhaq

Відповіді:


773

Ключем може бути функція, яка повертає кортеж:

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

20
Для повноти від timeit: для мене перший дав 6 нам за цикл, а другий 4,4 нас за цикл
Брайан Ларсен

10
Чи існує спосіб сортування першого за зростанням, а другого за спаданням? (Припустимо, що обидва атрибути є рядками, тому ніяких хакерів, як додавання -для цілих чисел, немає)
Martin Thoma

73
а як, якщо я хочу звернутися revrse=Trueлише до того, x[1]чи це можливо?
Еміт

28
@moose, @Amyth, щоб повернути лише один атрибут, ви можете сортувати двічі: спочатку за вторинним, s = sorted(s, key = operator.itemgetter(2))а потім за первинним s = sorted(s, key = operator.itemgetter(1), reverse=True)Не ідеально, але працює.
tomcounsell

52
@Amyth або інший варіант, якщо ключ - номер, щоб зробити його зворотним, ви можете помножити його на -1.
Серж

37

Я не впевнений, що це найбільш пітонічний метод ... У мене був список кортежів, яким потрібно сортувати 1-шу за низхідними цілими значеннями, а 2-го за алфавітом. Це вимагало змінити цілий сорт, але не алфавітний сортування. Ось моє рішення: (під час іспиту до речі, я навіть не знав, що ти можеш «гніздо» відсортувати функції)

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

13
Оскільки 2-е число - це працює так, як b = sorted(a, key = lambda x: (-x[1], x[0]))видно, за якими критеріями застосовується перший. що стосується ефективності, я не впевнений, комусь потрібно час.
Андрій-Нікулае Петре

5

Кілька років спізнюється на партію, але я хочу як сортувати за двома критеріями, так і використовувати reverse=True. Якщо хтось хоче знати, як це зробити, ви можете зафіксувати свої критерії (функції) в дужках:

s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)

5

Здається, ви можете використовувати 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 .

Це також дозволяє потенційно викласти вибір сортування назовні, яким слід поділитися споживачем, тестовим підрозділом або, можливо, їм сказати, як вони хочуть сортувати для певної операції у вашій програмі, лише надавши вам список, а не з'єднання їх з реалізацією на задньому кінці.


Хороша робота. Що робити, якщо атрибути слід сортувати за різними порядками? Припустимо, attrA слід сортувати за зростанням та attrB за спаданням? Чи є над цим швидке рішення? Дякую!
mhn_namak

1

Ось один із способів: ви в основному переписуєте функцію сортування, щоб взяти список функцій сортування, кожна функція сортування порівнює атрибути, які ви хочете перевірити, на кожному тесті сортування ви дивитесь і бачите, чи функція cmp повертає ненульове повернення якщо так, перервіть і надішліть повернене значення. Ви називаєте це, називаючи лямбда функції списку лямбда.

Його перевага полягає в тому, що він проходить через дані не на зразок попереднього сортування, як це роблять інші методи. Інша справа, що він сортує на місці, тоді як сортування, здається, робить копію.

Я використовував його для написання функції ранжування, яка займає ранжирування списку класів, де кожен об’єкт знаходиться в групі і має функцію оцінки, але ви можете додати будь-який список атрибутів. Зверніть увагу на не-лямбда-схоже, хоча хакітське використання лямбда для виклику сеттера. Ранкова частина не працюватиме для масиву списків, але сортування буде.

#First, here's  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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.