Clasificación personalizada de listas de Python

93

Estaba refactorizando un código antiguo mío y me encontré con esto:

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

El código funciona (¡y lo escribí hace unos 3 años!), Pero no puedo encontrar esto documentado en ningún lugar de los documentos de Python y todos lo usan sorted()para implementar la clasificación personalizada. ¿Alguien puede explicar por qué funciona esto?

Lorenzo
fuente
sorted()y sort()ofrecer clasificación personalizada de la misma manera, modulo la diferencia en la convención de llamadas.
Russell Borogove
2
De hecho, lo que sucede es que keyse prefiere usar un parámetro a pasar una cmpfunción. (El último ni siquiera está implementado en Python 3)
jsbueno
Es un poco ambiguo, depende de los elementos de la lista; su código requiere que tengan un atributo foo, de lo contrario explota. Es mejor definir un __lt__()método personalizado para su clase, entonces sorted()y list.sort()funcionará de inmediato. (Por cierto, los objetos ya no necesitan definirse __cmp__(), solo __lt__(). Vea esto
smci

Respuestas:

60

Está documentado aquí .

El método sort () toma argumentos opcionales para controlar las comparaciones.

cmp especifica una función de comparación personalizada de dos argumentos (elementos de la lista) que deben devolver un número negativo, cero o positivo dependiendo de si el primer argumento se considera más pequeño, igual o mayor que el segundo argumento: cmp = lambda x, y : cmp (x.lower (), y.lower ()). El valor predeterminado es Ninguno.

millas82
fuente
Gracias miles82 Estaba revisando aquí y no pude verlo en la firma del método docs.python.org/tutorial/datastructures.html
Lorenzo
No veo el mismo texto en la página a la que está vinculado. ¿Cambió la documentación? Además, cuando trato de usar cmp, obtengo TypeError: 'cmp' is an invalid keyword argument for this function. ¿Que esta pasando aqui?
Hola
1
@HelloGoodbye sort () no tiene un argumento cmp en Python 3. Esta es una respuesta antigua cuando el enlace de documentos era para Python 2. Puede encontrar los documentos antiguos aquí o leer más sobre ellos aquí . Si está usando Python 3, use el argumento clave en su lugar.
miles82
¿Y si realmente desea proporcionar una función de comparación? Quiero tratar los números en una cadena (de cualquier longitud, seleccionados con avidez) como símbolos, de manera equivalente a cómo se tratan los caracteres individuales. Sé cómo lograr eso trivialmente si puedo proporcionar una función de comparación, pero no si debo proporcionar una función clave. ¿Por qué cambió esto?
Hola
Supongo que aún se puede lograr si cada número contenido en la cadena se codifica utilizando una codificación que ordena los números lexicográficamente, como la codificación Levenshtein . Pero considero esto más como una solución al hecho de que sortno toma una función de comparación como argumento en Python 3, y no como algo que realmente me gustaría hacer.
Hola
104

Como nota al margen, aquí hay una mejor alternativa para implementar la misma clasificación:

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

O alternativamente:

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

Consulte el Cómo ordenar , es muy útil.

Andrew Clark
fuente
1
TIL sobre operador, muy útil.
incipiente
13

Como en este ejemplo. Quieres ordenar esta lista.

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

salida:

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

debe ordenar las tuplas por el segundo elemento, luego el primero:

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:

sólo sort(letter_cmp)

RryLee
fuente
4
¿Cómo sabe qué lista ordenar?
Cameron Monks
2
@CameronMonks yourList.sort (letter_cmp)
Minyc510
7

Esto no funciona en Python 3.

Sin embargo, puede usar functools cmp_to_key para que funcionen las funciones de comparación de estilo antiguo.

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)
El gato unfun
fuente
1

En python3 puedes escribir:

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
fuente
0

Aun mejor:

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

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

Steven
fuente