2012-06-26 10 views
24

el movimiento en las versiones recientes de Python para pasar una función clave a sort() del cmp anterior función es lo que hace más difícil para mí para realizar ordenaciones complejas en ciertos objetos.cómo escribir Python ordenar las funciones clave para descender los valores

Por ejemplo, quiero ordenar un conjunto de objetos del más nuevo al más antiguo, con un conjunto de campos de tie-breaker. Así que quiero las fechas en orden inverso, pero las cadenas en su orden natural. Con una función de comparación, puedo invertir la comparación del campo de fecha en comparación con los campos de cadena. Pero con una función clave, necesito encontrar alguna forma de invertir/invertir las fechas o las cuerdas.

Es fácil (aunque feo) hacer con los números, solo restarlos de algo, pero ¿tengo que encontrar un hack similar para las fechas (restarlas de otra fecha y comparar el timedeltas?) Y las cadenas (... No tengo idea de cómo invertiría su orden de una manera independiente de la configuración regional).

sé de la existencia de functools.cmp_to_key() pero se describe como siendo "que se utiliza principalmente como una herramienta de transición para programas que se convierte en Python 3 donde funciones de comparación ya no son compatibles". Esto implica que debería poder hacer lo que quiero con el método clave, pero ¿cómo?

+0

Pasando inversa = True sólo cambia cuál de las sub-claves tengo que encontrar una manera de invertir. Imagine (por alguna extraña razón) que quería ordenar por nombre ascendente y apellido descendiente, por ejemplo. Pasando reverse = True solo significa que tengo que encontrar una forma de invertir el nombre ahora, en lugar del apellido. – Kylotan

+0

http://python3porting.com/preparing.html#keycmp-section – wim

+0

No está claro cómo ayuda ese enlace. – Kylotan

Respuesta

9

La manera elegante lenta pero para hacer esto es crear un envoltorio de valor que se ha invertido el pedido:

from functools import total_ordering 
@total_ordering 
class ReversedOrder: 
    def __init__(self, value): 
     self.value = value 
    def __eq__(self, other): 
     return other.value == self.value 
    def __lt__(self, other): 
     return other.value < self.value 

Si usted no tiene functools.total_ordering, que tendría que poner en práctica los 6 comparaciones, por ejemplo:

import operator 
class ReversedOrder: 
    def __init__(self, value): 
     self.value = value 
for x in ['__lt__', '__le__', '__eq__', '__ne__', '__ge__', '__gt__']: 
    op = getattr(operator, x) 
    setattr(ReversedOrder, x, lambda self, other, op=op: op(other.value, self.value)) 
+0

Si el único uso para 'ReversedOrder' es para usar en una clave de clasificación, entonces solo necesita implementar' __lt__' y puede ignorar a todos los demás. – Duncan

+0

@Duncan ¿dónde está eso documentado? No puedo encontrarlo en la referencia. – ecatmur

+0

Consulte las notas de la versión de Python 3 http://docs.python.org/release/3.0.1/whatsnew/3.0.html#ordering-comparisons. Sin embargo, no sé dónde está en los documentos principales. – Duncan

4

Ordene dos veces, una vez en cada tecla y una vez invertida.

(Python sort es stable;., Es decir, no cambiar el orden de la lista original a menos que tenga a)

Se hace importa qué orden se hacen las clases en, si importa cómo se clasifican los elementos iguales

+0

Buena idea. Aún así, consideraría que es un poco hacky en comparación con una función de comparación personalizada, y no funciona bien en mi base de código (donde selecciono una clave de clasificación basada en la entrada y luego llamo 'sort()' una vez con ella) , pero sin duda es una forma de evitar el problema. – Kylotan

20

La forma más genérica de hacerlo es simplemente ordenar por separado cada tecla sucesivamente. clasificación de Python es siempre estable, por lo que es seguro hacer esto:

sort(data, key=tiebreakerkey) 
sort(data, key=datekey, reverse=True) 

será (suponiendo que las definiciones pertinentes para las funciones clave) le dan los datos ordenados por fecha descendente y ascendente criterios de desempate.

Tenga en cuenta que hacerlo de esta manera es más lento que producir una sola función de clave compuesta porque terminará haciendo dos tipos completos, por lo que si puede producir una clave compuesta que será mejor, pero dividirla en clases separadas da mucha flexibilidad: dada una función clave para cada columna, puede hacer cualquier combinación de ellas y especificar invertir para cualquier columna individual.

Para una opción totalmente genérica:

keys = [ (datekey, True), (tiebreakerkey, False) ] 
for key, rev in reversed(keys): 
    sort(data, key=key, reverse=rev) 

está completo y, aunque realmente creo que debe evitarse siempre que sea posible:

from functools import cmp_to_key 
sort(data, key=cmp_to_key(your_old_comparison_function)) 

La razón por la que creo que debería evitar esto se remontan tener n log n llamadas a la función de comparación en comparación con n llamadas a la función de la tecla (o 2n llama cuando hace las ordenaciones dos veces).

+0

+1 por la misma razón que katrielalex: soluciona el problema, aunque no creo que cuente como una función clave 'a', y creo que es menos elegante que una función de comparación. – Kylotan

+0

Aunque es más lento que una función de clave compuesta, probablemente aún será mucho más rápido que una función de comparación. También es posible escribir un envoltorio cmp_to_key que ajuste cualquier función de comparación para ser utilizado como una función clave, pero eso es realmente complicado – Duncan

+0

Hay una cmp_to_key en el módulo functools, por lo que podría argumentar que es más limpio que tener que generalizar a ordenamiento múltiple pasa. Pero sobre todo estoy sorprendido de que la opción 'cmp' haya sido eliminada cuando es claramente la forma más limpia de realizar algunos tipos. – Kylotan

11

Creo que los documentos están incompletos. Interpreto que la palabra "principalmente" significa que todavía hay razones para usar cmp_to_key, y esta es una de ellas.cmp se eliminó porque era una "molestia atractiva": la gente gravitaba sobre ella, aunque key era una mejor opción.

Pero su caso es claramente mejor como una función cmp, así que use cmp_to_key para implementarlo.

+0

+1 Estoy de acuerdo con esta respuesta, nada de malo con el uso de la solución ya provista por functools en aquellos casos (más raros) en los que utilizar una función cmp es la manera más elegante. – wim

+0

Me pregunto por qué esta función no solo fue sobrecargada para los dos casos de uso, incluso si la estructura de mapeo equivalente es posible. – jxramos

0

para cadena, puede utilizar algún valor máximo comúnmente reconocido (como 2^16 o 2^32) y el uso chr(), unicode(), ord() para hacer los cálculos, simplemente como para enteros.

En uno de mi trabajo, sé que tratar con cadenas en UTF-8 y sus ordinales están por debajo 0xffff, por lo que escribió:

def string_inverse(s): 
    inversed_string = '' 
    max_char_val = 0xffff 
    for c in s: 
     inversed_string += unicode(max_char_val-ord(c)) 
    return inversed_string   

result.sort(key=lambda x:(x[1], string_inverse(x[0])), reverse=True) 

x es de tipo: (cadena, int), así que lo que es obtener, de abusar del SQL:

select * from result order by x[1] desc, x[0] asc; 
Cuestiones relacionadas