Сортування списку Python за двома полями


172

У мене є такий список, створений із відсортованого csv

list1 = sorted(csv1, key=operator.itemgetter(1))

Насправді я хотів би сортувати список за двома критеріями: спочатку за значенням у полі 1, а потім за значенням у полі 2. Як це зробити?



Чи будемо ми залишати це питання вичерпним і просто обмежимо його сферу застосуванням "список-списків-довжини-два-вбудовані типи (наприклад, string / int / float)" . Або ми також дозволяємо "список-визначений користувачем-об'єкт" , як наводить заголовок, також дозволено, і в цьому випадку відповідь "Визначити __lt__()метод у вашому класі або успадкувати від якогось класу, який це робить" ? Це зробило б це набагато кращим канонічним.
smci

Відповіді:


157

подобається це:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))

1
+1: Більш елегантний, ніж мій. Я забув, що itemgetter може приймати кілька індексів.
dappawit

7
operatorце модуль, який потрібно імпортувати.
trapicki

3
як я буду діяти, якщо хочу сортувати висхідний елемент на один елемент та низхідний на інший, використовуючи itemgetter ??.
ашиш

3
@ashish, дивіться мою відповідь нижче з лямбда-функціями, це зрозуміло, сортуйте за "-x [1]" або навіть "x [0] + x [1]", якщо хочете
яп

що робити, якщо один критерій у зворотному режимі?
YaserKH

328

Не потрібно імпортувати нічого під час використання лямбда-функцій.
Наступні сортування listза першим елементом, потім за другим елементом.

sorted(list, key=lambda x: (x[0], -x[1]))

12
Приємно. Як ви зазначали в коментарі до головної відповіді вище, це найкращий (єдиний?) Спосіб робити кілька сортів із різними замовленнями на сортування. Можливо, виділити це. Також ваш текст не вказує на те, що ви сортували низхідний елемент за другим елементом.
PeterVermont

2
@ user1700890 Я припускав, що поле вже є рядком. Він повинен сортувати рядки в алфавітному порядку за замовчуванням. Ви повинні розмістити своє власне запитання окремо на ТА, якщо це не стосується конкретно відповіді тут або оригінального питання ОП.
pbible

5
що робить -в -x[1]стенді?
січень

7
@jan це зворотний сорт
jaap

3
Не працюватиме в одному конкретному випадку. Прийняте рішення також не буде працювати. Наприклад, стовпці, які використовуються як клавіші, - це всі рядки, які неможливо перетворити на числа. По-друге, хочеться сортувати у порядку зростання за одним стовпцем, а за низхідним порядком - за іншим.
coder.in.me

20

У Python є стабільний сорт, тому за умови, що продуктивність не є проблемою, найпростішим способом є сортування її за полем 2 та повторне сортування за полем 1.

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

list1 = sorted(csv1, key=operator.itemgetter(2))
list1 = sorted(list1, key=operator.itemgetter(1))

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

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

Тож якщо вам не потрібні жодні стовпці, відтворені в зворотному порядку, перейдіть до кількох аргументів до itemgetter, якщо це можливо, а стовпці не є числовими або ви хочете тримати сортування стабільним.

Редагувати: Для коментаторів, які мають проблеми з розумінням того, як це відповідає на початкове запитання, ось приклад, який точно показує, як стабільний характер сортування забезпечує те, що ми можемо робити окремі сортування на кожній клавіші і в кінцевому підсумку з даними, відсортованими за кількома критеріями:

DATA = [
    ('Jones', 'Jane', 58),
    ('Smith', 'Anne', 30),
    ('Jones', 'Fred', 30),
    ('Smith', 'John', 60),
    ('Smith', 'Fred', 30),
    ('Jones', 'Anne', 30),
    ('Smith', 'Jane', 58),
    ('Smith', 'Twin2', 3),
    ('Jones', 'John', 60),
    ('Smith', 'Twin1', 3),
    ('Jones', 'Twin1', 3),
    ('Jones', 'Twin2', 3)
]

# Sort by Surname, Age DESCENDING, Firstname
print("Initial data in random order")
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred''')
DATA.sort(key=lambda row: row[1])

for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.''')
DATA.sort(key=lambda row: row[2], reverse=True)
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.
''')
DATA.sort(key=lambda row: row[0])
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

Це приклад для запуску, але для того, щоб врятувати людей, які його виконують, вихід:

Initial data in random order
Jones      Jane       58
Smith      Anne       30
Jones      Fred       30
Smith      John       60
Smith      Fred       30
Jones      Anne       30
Smith      Jane       58
Smith      Twin2      3
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Jones      Twin2      3

First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Jones      Jane       58
Smith      Jane       58
Smith      John       60
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.
Smith      John       60
Jones      John       60
Jones      Jane       58
Smith      Jane       58
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.

Jones      John       60
Jones      Jane       58
Jones      Anne       30
Jones      Fred       30
Jones      Twin1      3
Jones      Twin2      3
Smith      John       60
Smith      Jane       58
Smith      Anne       30
Smith      Fred       30
Smith      Twin1      3
Smith      Twin2      3

Зокрема, зверніть увагу, як на другому кроці reverse=Trueпараметр підтримує імена в порядку, тоді як просто сортування та повернення списку втратить потрібний порядок для третього ключа сортування.


1
Стабільне сортування не означає, що воно не забуде, яким було ваше попереднє сортування. Ця відповідь неправильна.
Майк Аксіак

7
Стабільне сортування означає, що ви можете сортувати за стовпцями a, b, c, просто сортуючи стовпці c, а потім b, a. Якщо ви не хочете розширити свій коментар, я думаю, що ви помиляєтесь.
Дункан

7
Ця відповідь, безумовно, правильна, хоча для більш великих списків вона однозначна: якщо список був частково відсортований, ви втратите більшу частину оптимізації сортування Python, переміщаючи список навколо набагато більше. @Mike, ти невірний; Я пропоную перевірити відповіді, перш ніж визнати їх неправильними.
Глен Мейнард

6
@MikeAxiak: docs.python.org/2/library/stdtypes.html#index-29 констатується в коментарі 9: Починаючи з Python 2.3, метод sort () гарантовано стабільний. Сорт є стабільним, якщо він гарантує не змінювати відносний порядок елементів, які порівнюють рівними - це корисно для сортування за декількома пропусками (наприклад, сортування за відділом, потім за рівнем зарплати).
trapicki

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

14
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )

4
Я не думаю, що tuple()можна отримати два аргументи (вірніше, три, якщо порахувати self)
Філіпе Коррея

3
кортеж бере лише може взяти один аргумент
therealprashant

1
returnЗаява повинна бути return tuple((x[1], x[2]))або просто return x[1], x[2]. Якщо ви шукаєте сортування в різних напрямках, зверніться до @jaap у відповідь нижче
Джо Качікараран

… Або tuple(x[1:3]), якщо ви хочете з якоїсь причини використовувати конструктор кортежів, а не просто перелік кортежів x[1], x[2]. Або keyfunc = operator.itemgetter(1, 2)навіть не пишіть функцію самостійно.
abarnert

3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

Ми також можемо використовувати .sort з лямбда 2 рази, тому що сорт пітона є на місці та стабільний. Це спочатку буде сортувати список відповідно до другого елемента, x [1]. Потім він сортує перший елемент, x [0] (найвищий пріоритет).

employees[0] = Employee's Name
employees[1] = Employee's Salary

Це еквівалентно виконанню наступних дій: staff.sort (key = лямбда x: (x [0], x [1]))


1
ні, це правило сортування потрібно мати перевагу над другим.
CodeFarmer

1

У порядку зростання Ви можете використовувати:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]))

або у порядку зменшення ви можете використовувати:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]),reverse=True)

0

Сортування списку диктів за допомогою наведеного нижче буде сортувати список у порядку зменшення у першому стовпці як зарплата та другому стовпчику як вік

d=[{'salary':123,'age':23},{'salary':123,'age':25}]
d=sorted(d, key=lambda i: (i['salary'], i['age']),reverse=True)

Вихід: [{'зарплата': 123, 'вік': 25}, {'зарплата': 123, 'вік': 23}]

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.