raw_input - print en python
Cómo escribir las funciones de las teclas de clasificación de Python para valores descendentes (6)
El movimiento en las versiones recientes de Python para pasar una función clave para sort()
de la función cmp anterior hace que sea más difícil para mí realizar clasificaciones complejas en ciertos objetos.
Por ejemplo, quiero ordenar un conjunto de objetos del más reciente al más antiguo, con un conjunto de campos de desempate de cadenas. Así que quiero las fechas en orden inverso, pero las cadenas en su orden natural. Con una función de comparación, simplemente puedo revertir la comparación para el 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 cadenas.
Es fácil (aunque feo) hacer con números, solo restarlos de algo, pero ¿tengo que encontrar un hack similar para las fechas (restarlos de otra fecha y comparar las timedeltas?) Y cadenas (... no tengo idea cómo revertiría su orden de manera independiente de la localización).
Sé de la existencia de functools.cmp_to_key()
pero se describe como "utilizado principalmente como una herramienta de transición para los programas que se convierten a Python 3 donde las 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?
Creo que los documentos están incompletos. Interpreto la palabra "principalmente" para significar 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 gravitaría, aunque la 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.
La forma lenta pero elegante de hacer esto es crear una envoltura de valor que haya revertido el orden:
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 no tiene functools.total_ordering
, tendría que implementar las 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))
La forma más genérica de hacer esto es simplemente ordenar por separado cada tecla a su vez. La clasificación de Python siempre es estable, por lo que es seguro hacer esto:
sort(data, key=tiebreakerkey)
sort(data, key=datekey, reverse=True)
(asumiendo las definiciones relevantes para las funciones clave) le dará los datos ordenados por fecha descendente y desempate ascendente.
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 clases completas, así que si puede producir una clave compuesta que sea mejor, pero dividirla en clases separadas le da mucha flexibilidad : dada una función clave para cada columna, puede hacer cualquier combinación de ellas y especificar el reverso para cualquier columna individual.
Para una opción completamente genérica:
keys = [ (datekey, True), (tiebreakerkey, False) ]
for key, rev in reversed(keys):
sort(data, key=key, reverse=rev)
y para ser completo, aunque realmente creo que debería 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ías evitar esto es volver a tener n log n
llamadas a la función de comparación en comparación con n
llamadas a la función de tecla (o 2n
llamadas cuando haces la ordenación dos veces).
Ordenar dos veces, una vez en cada tecla y una vez invertida.
(La sort
Python es stable ; es decir, no cambia el orden de la lista original a menos que sea necesario).
No importa en qué orden haga las clasificaciones, si le importa cómo se ordenan los elementos iguales.
Para String, puede usar un valor máximo comúnmente reconocido (como 2 ^ 16 o 2 ^ 32) y usar chr (), unicode (), ord () para hacer los cálculos, como para los enteros.
En uno de mis trabajos, sé que trato con cadenas en utf8 y sus ordinales están por debajo de 0xffff, así que escribí:
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 obtengo es abusar del SQL:
select * from result order by x[1] desc, x[0] asc;
Una forma es usar la biblioteca de pandas
y args ascending
, estableciendo las columnas que desea ordenar ascendente y las columnas que desea descendente haciendo, por ejemplo, ascending=[True,False,False]
Puede hacerlo no solo para dos niveles (por ejemplo, datetime
y str
) sino para cualquier número de niveles necesarios.
Por ejemplo, si tiene
d = [[1, 2, datetime(2017,1,2)],
[2, 2, datetime(2017,1,4)],
[2, 3, datetime(2017,1,3)],
[2, 3, datetime(2017,1,4)],
[2, 3, datetime(2017,1,5)],
[2, 4, datetime(2017,1,1)],
[3, 1, datetime(2017,1,2)]]
Puedes configurar tu df
df = pd.DataFrame(d)
y usar sort_values
sorted_df = df.sort_values(by=[0,1,2], ascending=[True,False,False])
sorted_list = sorted_df.agg(list, 1).tolist()
[[1, 2, Timestamp(''2017-01-02 00:00:00'')],
[2, 4, Timestamp(''2017-01-01 00:00:00'')],
[2, 3, Timestamp(''2017-01-05 00:00:00'')],
[2, 3, Timestamp(''2017-01-04 00:00:00'')],
[2, 3, Timestamp(''2017-01-03 00:00:00'')],
[2, 2, Timestamp(''2017-01-04 00:00:00'')],
[3, 1, Timestamp(''2017-01-02 00:00:00'')]]
Observe que la primera columna está ordenada en forma ascendente, y la segunda y tercera están en orden descendente, lo que, por supuesto, se debe a la configuración ascending=[True,False,False]
.