2009-11-01 10 views
5

Tengo una lista de tuplas que estoy tratando de ordenar y podría necesitar ayuda.Ayuda de clasificación: primero por esto, y luego por eso

El campo que quiero ordenar en las tuplas parece "XXX_YYY". Primero, quiero agrupar los valores XXX en orden inverso, y luego, dentro de esos grupos, quiero colocar los valores YYY en orden de clasificación normal. (NOTA: Estoy tan feliz, en realidad, la clasificación del segundo elemento de la tupla de esta manera, invertir el orden primera palabra, orden normal segundo.)

Aquí es un ejemplo de lo que tengo y lo Me gustaría al final ... no estoy seguro de cómo hacerlo.

mylist = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

me gustaría realizar algún tipo de sort() en esta lista que va a cambiar la salida a:

sorted = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

Sospecho que puede haber una manera Pythonic para manejar esto, pero no soy capaz de envolver mi cabeza alrededor.

Respuesta

8

personalizada comparación funciones de separación, como se sugiere en las respuestas existentes, hacen que sea fácil de ordenar en una mezcla de ascenso y pedidos descendentes, pero tienen problemas graves de rendimiento y han sido eliminados en Python 3, dejando solo el enfoque de personalización preferido: funciones de extracción de clave ... mucho más rápidas, aunque más delicadas de usar para el caso de uso relativamente raro de mixto tipos ascendentes/descendentes.

En Python 2.*, que apoya a uno u otro tipo de personalización (no tanto en la misma llamada a sort o sorted :-), una función de comparación de encargo se puede pasar como un argumento con nombre cmp=; o, una función de extracción de clave personalizada se puede pasar como un argumento con nombre key=. En Python 3.*, solo la última opción está disponible.

Definitivamente vale la pena entender el enfoque de extracción de claves, incluso si crees que acabas de resolver el problema con un enfoque de comparación personalizada: no solo por el rendimiento, sino por el futuro (Python 3) y por la generalidad (El enfoque key= también se aplica a min, max, itertools.groupby ... ¡mucho más general que el enfoque cmp=!).

La extracción de claves es muy simple cuando todos los subcampos clave deben ordenarse de la misma manera (todos ascendentes, o todos descendentes); solo los extrae; aún es bastante fácil si los subcampos que van "al revés" son números (solo cambias su signo al extraer); el caso delicado es exactamente el que tiene: múltiples campos de cadena que deben compararse de manera opuesta.

Un enfoque bastante simple para resolver su problema es una pequeña clase de cuña:

class Reverser(object): 
    def __init__(self, s): self.s = s 
    def __lt__(self, other): return other.s < self.s 
    def __eq__(self, other): return other.s == self.s 

en cuenta que sólo tiene que suministrar __lt__ y __eq__ (los < y == operadores) - sort y amigos sintetizar todos los demás comparaciones, si es necesario, basadas en esos dos.

Así que, armados con esta pequeña herramienta auxiliar, se puede proceder fácilmente ...:

def getkey(tup): 
    a, b = tup[0].split('_') 
    return Reverser(a), b 

my_list.sort(key=getkey) 

Como se ve, una vez que "obtener" el inversor y los conceptos clave de extracción, que pagan esencialmente no tiene precio usando extracción de clave en lugar de comparación personalizada: el código que sugiero son 4 declaraciones para la clase de reversor (que puede escribir una vez y poner en su módulo "golosinas"), tres para la función de extracción de clave y, por supuesto, una para el sort o sorted llamada: un total de ocho frente a 4 + 1 == 5 del enfoque de comparación personalizado en la forma más compacta (es decir, la que utiliza ya sea cmp con un cambio de signo o cmp con argumento modificado) nts). ¡Tres declaraciones no son un gran precio para pagar por las ventajas de la extracción de claves! -)

El rendimiento no es un gran problema con una lista tan corta, pero con una cantidad modestamente más larga (10 veces) ...:

# my_list as in the Q, my_cmp as per top A, getkey as here 

def bycmp(): 
    return sorted(my_list*10, cmp=my_cmp) 

def bykey(): 
    return sorted(my_list*10, key=getkey) 

... 

$ python -mtimeit -s'import so' 'so.bykey()' 
1000 loops, best of 3: 548 usec per loop 
$ python -mtimeit -s'import so' 'so.bycmp()' 
1000 loops, best of 3: 995 usec per loop 

es decir, el enfoque key= ya está mostrando una ganancia de rendimiento de casi dos veces (clasificación de la lista dos veces más rápido) cuando se trabaja en una lista de 50 elementos - bien vale la pena el módico precio de 8 líneas" en vez de 5 ", particularmente con todas las otras ventajas que ya mencioné!

+0

Guau, me gusta su solución. No sabía que el enfoque cmp = tenía tal penalización. –

+0

@Steven, tx - sí, no todos entienden por qué cmp = se eliminó en Python 3 (como una "molestia atractiva" que tienta a las personas a sufrir una penalización de rendimiento! -), es exactamente por eso que publiqué esta explicación detallada, así que gracias ¡para confirmar puede ayudar! -) –

+2

@Alex: dudo en editar una de * tus * respuestas, pero tal vez mi_list.key (cmp = my_cmp) debería ser my_list.sort (key = getkey)? –

10
def my_cmp(x, y): 
    x1, x2 = x[0].split('_') 
    y1, y2 = y[0].split('_') 
    return -cmp(x1, y1) or cmp(x2, y2) 

my_list = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

sorted_list = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

my_list.sort(cmp=my_cmp) 
assert my_list == sorted_list 
+1

Estaba a punto de editar la mina para anular la llamada cmp en su lugar cuando publicó su respuesta. :) – Kylotan

+1

He simplificado la comparación más a '-cmp (x1, y1) o cmp (x2, y2)'. :) –

+0

también podría pasar el argumento clave para ordenar y deshacerse de la división en la parte superior de su función: mi_lista.sort (cmp = mi_cmp, clave = lambda x: x [0] .split ('_')) –

2
>>> def my_cmp(tuple_1, tuple_2): 
    xxx_1, yyy_1 = tuple_1[0].split('_') 
    xxx_2, yyy_2 = tuple_2[0].split('_') 
    if xxx_1 > xxx_2: 
     return -1 
    elif xxx_1 < xxx_2: 
     return 1 
    else: 
     return cmp(yyy_1, yyy_2) 


>>> import pprint 
>>> pprint.pprint(sorted(mylist, my_cmp)) 
[(u'kf_magazine', u'KF: Magazine'), 
(u'kf_news', u'KF: News & Information'), 
(u'kf_video', u'KF: Video'), 
(u'community_news', u'Community: News & Information'), 
(u'community_video', u'Community: Video')] 
No

la solución más bonita del mundo ...

Cuestiones relacionadas