Classificação de lista Python personalizada

93

Eu estava refatorando um código antigo meu e me deparei com isto:

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

O código funciona (e eu o escrevi há cerca de 3 anos!), Mas não consigo encontrar isso documentado em nenhum lugar nos documentos do Python e todo mundo usa sorted()para implementar classificação personalizada. Alguém pode explicar por que isso funciona?

Lorenzo
fonte
sorted()e sort()oferecem classificação personalizada da mesma maneira, modulo a diferença na convenção de chamada.
Russell Borogove
2
Na verdade, o que acontece é que usar um keyparâmetro é preferível a passar uma cmpfunção. (O último nem mesmo é implementado em Python 3)
jsbueno
É meio ambíguo, depende de quais são os itens da lista; seu código requer que eles tenham um atributo foo, caso contrário, ele explode. Melhor para definir um personalizado __lt__()método para a sua classe, em seguida, sorted()e list.sort()vai funcionar out-of-the-box. (A propósito, os objetos não precisam mais ser definidos __cmp__(), apenas __lt__(). Veja isto
smci

Respostas:

60

Está documentado aqui .

O método sort () aceita argumentos opcionais para controlar as comparações.

cmp especifica uma função de comparação personalizada de dois argumentos (itens de lista) que deve retornar um número negativo, zero ou positivo, dependendo se o primeiro argumento é considerado menor, igual ou maior que o segundo argumento: cmp = lambda x, y : cmp (x.lower (), y.lower ()). O valor padrão é nenhum.

miles82
fonte
Obrigado miles82 Eu estava verificando aqui e não consegui ver na assinatura do método docs.python.org/tutorial/datastructures.html
Lorenzo
Não vejo o mesmo texto na página que você vinculou. A documentação mudou. Além disso, quando tento usar cmp, consigo TypeError: 'cmp' is an invalid keyword argument for this function. O que está acontecendo aqui?
HelloGoodbye
1
@HelloGoodbye sort () não tem um argumento cmp no Python 3. Essa é uma resposta antiga quando o link de documentos era para Python 2. Você pode encontrar os documentos antigos aqui ou ler mais sobre isso aqui . Se você estiver usando Python 3, use o argumento chave .
miles82 de
E se você realmente quiser fornecer uma função de comparação? Quero tratar os números em uma string (de qualquer comprimento, escolhida avidamente) como símbolos, de forma equivalente a como os caracteres individuais são tratados de outra forma. Sei como fazer isso trivialmente se puder fornecer uma função de comparação, mas não se tiver de fornecer uma função-chave. Por que isso foi alterado?
HelloGoodbye
Acho que isso ainda pode ser alcançado se cada número contido na string for codificado usando uma codificação que ordena os números lexicograficamente, como a codificação de Levenshtein . Mas considero isso mais como uma solução alternativa para o fato de que sortuma função de comparação não é considerada como argumento no Python 3, e não como algo que eu realmente gostaria de fazer.
HelloGoodbye
104

Como observação lateral, aqui está uma alternativa melhor para implementar a mesma classificação:

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

Ou alternativamente:

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

Confira o Guia de Classificação , é muito útil.

Andrew Clark
fonte
1
TIL sobre o operador, muito útil.
disparado em
13

Assim como este exemplo. Você quer classificar esta lista.

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

resultado:

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

você deve classificar as tuplas pelo segundo item, depois o primeiro:

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

Finalmente:

somente sort(letter_cmp)

RryLee
fonte
4
Como ele sabe qual lista classificar?
Cameron Monks
2
@CameronMonks yourList.sort (letter_cmp)
Minyc510
7

Isso não funciona no Python 3.

Você pode usar functools cmp_to_key para que as funções de comparação do estilo antigo funcionem.

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)
The Unfun Cat
fonte
1

Em python3, você pode escrever:

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

alist = sorted(alist, key=cmp_to_key(cmp_items))
mece1390
fonte
0

Melhor ainda:

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

Retirado de: https://docs.python.org/3/howto/sorting.html

Steven
fonte