Пользовательская сортировка списков Python

98

Я реорганизовал какой-то свой старый код и наткнулся на это:

alist.sort(cmp_items)

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

Код работает (и я написал его около 3 лет назад!), Но я не могу найти эту вещь, задокументированную где-либо в документации Python, и все используют sorted()для реализации настраиваемой сортировки. Может кто-нибудь объяснить, почему это работает?

Лоренцо
источник
sorted()и sort()предлагают настраиваемую сортировку почти таким же образом, с учетом разницы в соглашениях о вызовах.
Рассел Борогов
2
Действительно, случается так, что использование keyпараметра предпочтительнее передачи cmpфункции. (Последний даже не реализован в Python 3)
jsbueno
Это как-то неоднозначно, зависит от того, какие элементы были в списке; ваш код требует, чтобы у них был атрибут foo, иначе он взорвется. Лучше определить собственный __lt__()метод для вашего класса, тогда он sorted()и list.sort()будет работать «из коробки». (Кстати, объекты больше не нужно определять __cmp__(), просто __lt__(). См. Это
smci

Ответы:

60

Это задокументировано здесь .

Метод sort () принимает необязательные аргументы для управления сравнениями.

cmp определяет настраиваемую функцию сравнения двух аргументов (элементов списка), которая должна возвращать отрицательное, нулевое или положительное число в зависимости от того, считается ли первый аргумент меньшим, равным или большим, чем второй аргумент: cmp = lambda x, y : cmp (x.lower (), y.lower ()). Значение по умолчанию - Нет.

миль82
источник
Спасибо, miles82. Я проверял здесь и не увидел его в сигнатуре метода docs.python.org/tutorial/datastructures.html
Лоренцо,
Я не вижу того же текста на странице, на которую вы ссылаетесь. Изменилась ли документация. Кроме того, когда я пытаюсь использовать cmp, я получаю TypeError: 'cmp' is an invalid keyword argument for this function. Что здесь происходит?
Hellogoodbye
2
@HelloGoodbye sort () не имеет аргумента cmp в Python 3. Это старый ответ, когда ссылка на документы была для Python 2. Вы можете найти старые документы здесь или узнать больше об этом здесь . Если вы используете Python 3, используйте вместо него ключевой аргумент .
miles82
А что, если вы действительно хотите предоставить функцию сравнения? Я хочу обрабатывать числа в строке (любой длины, выбранной с жадностью) как символы, что эквивалентно тому, как иначе обрабатываются отдельные символы. Я знаю, как добиться этого тривиально, если я могу предоставить функцию сравнения, но не если я должен предоставить ключевую функцию. Почему это было изменено?
Hellogoodbye
Я предполагаю, что это все еще может быть достигнуто, если каждое число, содержащееся в строке, закодировано с использованием кодировки, которая упорядочивает числа лексикографически, например, кодирование Левенштейна . Но я рассматриваю это скорее как обходной путь к тому факту, что sortфункция сравнения не принимает в качестве аргумента в Python 3, а не как то, что я действительно хотел бы сделать.
Hellogoodbye
108

В качестве побочного примечания, вот лучшая альтернатива реализации той же сортировки:

alist.sort(key=lambda x: x.foo)

Или альтернативно:

import operator
alist.sort(key=operator.attrgetter('foo'))

Ознакомьтесь с руководством по сортировке , это очень полезно.

Эндрю Кларк
источник
1
TIL об операторе, очень полезно.
Ffledgling
16

Прямо как в этом примере. Вы хотите отсортировать этот список.

[('c', 2), ('b', 2), ('a', 3)]

выход:

[('a', 3), ('b', 2), ('c', 2)]

вы должны отсортировать кортежи по второму элементу, затем по первому:

def letter_cmp(a, b):
    if a[1] > b[1]:
        return -1
    elif a[1] == b[1]:
        if a[0] > b[0]:
            return 1
        else:
            return -1
    else:
        return 1

Затем преобразуйте его в ключевую функцию:

from functools import cmp_to_key
letter_cmp_key = cmp_to_key(letter_cmp))

Теперь вы можете использовать свой собственный порядок сортировки:

[('c', 2), ('b', 2), ('a', 3)].sort(key=letter_cmp_key)
RryLee
источник
4
Как он узнает, какой список сортировать?
Кэмерон Монкс
2
@CameronMonks yourList.sort (letter_cmp)
kebab-case
7

Это не работает в Python 3.

Вы можете использовать functools cmp_to_key, чтобы старые функции сравнения работали.

from functools import cmp_to_key

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

cmp_items_py3 = cmp_to_key(cmp_items)

alist.sort(cmp_items_py3)
Несчастный Кот
источник
1

Я знаю, что многие уже опубликовали несколько хороших ответов. Однако я хочу предложить один хороший и простой метод без импорта какой-либо библиотеки.

l = [(2, 3), (3, 4), (2, 4)]
l.sort(key = lambda x: (-x[0], -x[1]) )
print(l)
l.sort(key = lambda x: (x[0], -x[1]) )
print(l)

Выход будет

[(3, 4), (2, 4), (2, 3)]
[(2, 4), (2, 3), (3, 4)]

Вывод будет отсортирован в соответствии с порядком параметров, которые мы предоставили в формате кортежа.

Peddiashrith
источник
0

Даже лучше:

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Взято с: https://docs.python.org/3/howto/sorting.html

Стивен
источник