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


173

У меня есть следующий список, созданный из отсортированного CSV

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

Я бы хотел отсортировать список по двум критериям: сначала по значению в поле 1, а затем по значению в поле 2. Как мне это сделать?



Оставим ли мы этот вопрос стоящим и просто ограничим его область видимости "list-of-lists-of-length-two-встроенные типы (например, string / int / float)" . Или мы также разрешаем «список пользовательских объектов» , как предполагает заголовок, и в этом случае ответом будет «Определить __lt__()метод для вашего класса или наследовать от какого-то класса, который это делает» ? Это сделало бы его намного лучше каноническим.
smci

Ответы:


158

как это:

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

1
+1: более элегантно, чем у меня. Я забыл, что itemgetter может принимать несколько индексов.
dappawit

7
operatorэто модуль, который нужно импортировать
trapicki

3
как мне поступить, если я хочу отсортировать по возрастанию по одному элементу и по убыванию по другому, используя itemgetter ??.
Ashish

3
@ashish, смотрите мой ответ ниже с лямбда-функциями, это понятно, сортируйте по «-x [1]» или даже «x [0] + x [1]», если хотите
jaap

что делать, если один критерий в обратном режиме?
ЯзерХ

328

Не нужно ничего импортировать при использовании лямбда-функций.
Следующие сортировки listпо первому элементу, затем по второму элементу.

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

12
Ницца. Как вы отметили в комментарии к основному ответу выше, это лучший (только?) Способ сделать несколько сортировок с разными порядками сортировки. Возможно, выделите это. Также в вашем тексте не указано, что вы отсортировали по убыванию по второму элементу.
PeterVermont

2
@ user1700890 Я предполагал, что поле уже было строкой. Он должен сортировать строки в алфавитном порядке по умолчанию. Вы должны опубликовать свой собственный вопрос отдельно в SO, если он не связан конкретно с ответом здесь или с оригинальным вопросом OP.
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, перетасовывая список вокруг еще большего количества. @ Майк, ты не прав; Я предлагаю на самом деле проверить ответы, прежде чем объявлять их неправильными.
Гленн Мейнард

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
Кортеж принимает только может принять один аргумент
великолепный

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 раза, потому что сортировка Python установлена ​​и стабильна. Сначала будет отсортирован список по второму элементу x [1]. Затем будет отсортирован первый элемент, x [0] (самый высокий приоритет).

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

Это эквивалентно выполнению следующих действий: employee.sort (key = lambda 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)

Вывод: [{'salary': 123, 'age': 25}, {'salary': 123, 'age': 23}]

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.