2012-04-12 11 views
10

que tienen la siguiente matriz de Python diccionarios:Python: ordena una matriz de diccionarios con un comparador personalizado?

myarr = [ { 'name': 'Richard', 'rank': 1 }, 
{ 'name': 'Reuben', 'rank': 4 }, 
{ 'name': 'Reece', 'rank': 0 }, 
{ 'name': 'Rohan', 'rank': 3 }, 
{ 'name': 'Ralph', 'rank': 2 }, 
{ 'name': 'Raphael', 'rank': 0 }, 
{ 'name': 'Robin', 'rank': 0 } ] 

me gustaría resolverlo por los valores de rango, el pedido de la siguiente manera: 1-2-3-4-0-0-0.

si trato:

sorted_master_list = sorted(myarr, key=itemgetter('rank')) 

continuación, la lista está ordenada en el orden 0-0-0-1-2-3-4.

¿Cómo puedo definir una función de comparador personalizada para llevar los ceros al final de la lista? Me pregunto si puedo usar algo como methodcaller.

Respuesta

23

Opción 1:

key=lambda d:(d['rank']==0, d['rank']) 

Opción 2:

key=lambda d:d['rank'] if d['rank']!=0 else float('inf') 

Demostración:

"Me me gusta ordenarlo por los valores de rango, ordenando de la siguiente manera: 1-2-3-4-0-0-0. " carteles --original

>>> sorted([0,0,0,1,2,3,4], key=lambda x:(x==0, x)) 
[1, 2, 3, 4, 0, 0] 

>>> sorted([0,0,0,1,2,3,4], key=lambda x:x if x!=0 else float('inf')) 
[1, 2, 3, 4, 0, 0] 

 

Comentarios adicionales:?

"Por favor, ¿podría explicar a mí (un principiante de Python) lo que está haciendo lo que puedo ver que es una lambda, que sé que es una función anónima: ¿qué hay entre paréntesis?"- comentario OP

Indexación/notación de corte:

itemgetter('rank') es lo mismo que lambda x: x['rank'] es lo mismo que la función:

def getRank(myDict): 
    return myDict['rank'] 

El [...] se llama la indexación/notación de división, consulte Explain Python's slice notation - También tenga en cuenta que someArray[n] es una notación común en muchos lenguajes de programación para la indexación, pero puede no admitir sectores del formulario [start:end] o [start:end:step].

key= vs cmp= vs rica comparación:

En cuanto a lo que está pasando, hay dos formas comunes para especificar cómo funciona un algoritmo de ordenación: uno es con una función key, y el otro es con una cmp función (ahora obsoleto en Python, pero mucho más versátil). Mientras que una función cmp le permite especificar arbitrariamente cómo se deben comparar dos elementos (entrada: a, b; salida: a<b o a>b o a==b). Aunque es legítimo, no nos da ningún beneficio importante (tendríamos que duplicar el código de una manera incómoda), y una función clave es más natural para su caso. (Ver "objeto rica comparación" de cómo definir implícitamente cmp= de una manera elegante, pero posiblemente sea excesiva.)

la implementación de su función clave:

Desafortunadamente 0 es un elemento de los números enteros y por lo tanto tiene una orden natural: 0 es normalmente < 1,2,3 ... Por lo tanto, si queremos imponer una regla adicional, tenemos que ordenar la lista en un "nivel superior". Hacemos esto haciendo que la tecla sea una tupla: las tuplas se ordenan primero por su primer elemento, luego por su segundo elemento. True siempre se ordenará después de False, por lo que todos los Trues se ordenarán después de los Falses; luego se ordenarán de forma normal: (True,1)<(True,2)<(True,3)<..., (False,1)<(False,2)<..., (False,*)<(True,*). La alternativa (opción 2) simplemente asigna a los diccionarios de rango 0 un valor de infinito, ya que se garantiza que está por encima de cualquier rango posible.

Más en general alternativa - objetar rica comparación:

La solución aún más general sería la creación de una clase que representa los registros, a continuación, aplicar __lt__, __gt__, __eq__, __ne__, __gt__, __ge__, y todos los demás rich comparison operators, o alternativamente solo implemente uno de esos y __eq__ y use el @functools.total_ordering decorator. Esto hará que los objetos de esa clase utilicen la lógica personalizada siempre que utilice operadores de comparación (por ejemplo, x=Record(name='Joe', rank=12)y=Record(...)x<y); dado que la función sorted(...) usa < y otros operadores de comparación por defecto en una ordenación de comparación, esto hará que el comportamiento sea automático al ordenar, y en otros casos donde use < y otros operadores de comparación. Esto puede o no ser excesivo dependiendo de su caso de uso.

Limpiador alternativa - no sobrecargue 0 con la semántica:

debo señalar sin embargo que es un poco artificial para poner detrás de 0s 1,2,3,4, etc. Si esto se justifica depende de si rank = 0 realmente significa rank = 0; si rank = 0 son realmente "inferiores" a rank = 1 (que a su vez son realmente "inferiores" a rank = 2 ...). Si este es realmente el caso, entonces su método está perfectamente bien. Si este no es el caso, puede considerar omitir la entrada 'rank':... en lugar de establecer 'rank':0. Posteriormente, se podría solucionar por la respuesta de Lev Levitsky usando 'rank' in d, o por:

Opción 1 con diferente esquema:

key=lambda d: (not 'rank' in d, d['rank']) 

Opción 2 con diferente esquema:

key=lambda d: d.get('rank', float('inf')) 

sidenote: Basándose en el la existencia de infinito en python es casi un hack en el límite, por lo que cualquiera de las soluciones mencionadas (tuplas, comparación de objetos), filter-then-concatenate solution de Lev, e incluso tal vez el cmp solution ligeramente más complicado (escrito por wilson), más generalizable a otros idiomas.

+0

Por favor explique su downvote incorrecto. =) – ninjagecko

+0

Opción 1 funciona! Gracias. – Richard

+0

¿Podría explicarme (un principiante de Python) qué está haciendo? Puedo ver que es una lambda, que sé que es una función anónima: ¿qué hay entre paréntesis? – Richard

-2

Simplemente dé paso a "clave" una función arbitraria u objeto invocable - es lo que se necesita. itemgetter pasa a ser una de esas funciones, pero puede funcionar con cualquier función que escriba; solo tiene que tomar un único parámetro como entrada, y devolver un objeto que es directamente compatible para lograr el orden que desee.

En este caso:

def key_func(item): 
    return item["rank"] if item["rank"] != 0 else -100000 

sorted_master_list = sorted(myarr, key=key_func) 

(que también se puede escribir como una expresión lambda)

-3

Puede utilizar la función de parámetro clave:

de culo clasificar:

sorted_master_list = sorted(myarr, key=lambda x: x.get('rank')) 

o al desc:

sorted_master_list = sorted(myarr, key=lambda x: -x.get('rank')) 

También se puede leer acerca de la función ordenados aquí http://wiki.python.org/moin/HowTo/Sorting

+0

wow, ¿por qué -2 ??? – Rustem

+0

Porque sugirió una clasificación ascendente o descendente estándar, y OP desea algo ligeramente diferente (orden de clasificación normal EXCEPTO con los elementos de rango cero ordenados de manera diferente). Debe leer y comprender la pregunta original antes de publicar respuestas genéricas. –

-1

Una manera hacky de hacerlo es:

sorted_master_list = sorted(myarr, key=lambda x: 99999 if x['rank'] == 0 else x['rank']) 

Esto funciona bastante bien si sabe que su rango máximo.

+0

Esto no funcionará. 'itemgetter()' devuelve una * función *. Cuando ya usas una lambda, simplemente usa 'x ['rank']' - de todos modos, pierdes la ventaja de rendimiento de usar 'itemgetter'. – ThiefMaster

+0

@ThiefMaster true – mensi

-3

tratar sorted_master_list = ordenados (myarr, clave = itemgetter ('ranking'), revertir = True)

+0

Esto daría un orden de '4, 3, 2, 1, 0, 0, 0'. El OP quería '1, 2, 3, 4, 0, 0, 0'. –

1

que lo haría

sortedlist = sorted([x for x in myarr if x['rank']], key=lambda x: x['rank']) + [x for x in myarr if not x['rank']] 

poco supongo que podría ser comprimido de alguna manera.

1

estoy más inclinado hacia la creación de una función de comparación para manejar el "0", específicamente:

def compare(x,y): 
    if x == y: 
     return 0 
    elif x == 0: 
     return 1 
    elif y == 0: 
     return -1 
    else: 
     return cmp(x,y) 

sorted(myarr, cmp=lambda x,y: compare(x,y), key=lambda x:x['rank']) 

Sin embargo, hay penalización en el rendimiento de la costumbre función de comparación.

+0

Esta es la solución 'cmp'. Me gusta el uso combinado de 'key =' y 'cmp =' como una explotación elegante de la forma en que los parámetros de 'sorted' funcionan en python. En inglés esto dice "' comparando elementos left, right by rank: son iguales si sus rangos son iguales, de lo contrario left es más grande si es 0 o right es más grande si es 0, de lo contrario realiza la comparación predeterminada' ". Trágicamente, las dos primeras líneas son necesarias, de lo contrario, las líneas intermedias devolverían valores incorrectos antes del 'cmp' final. La alternativa sería eliminar las dos líneas superiores, y hacer: 'si x == 0 y y! = 0' ...' elif y = 0 yx! = 0' ... 'else:'. – ninjagecko

+1

Vale la pena señalar que 'cmp' se va en Python 3. – senderle

-1

Estás myarr vinculante aquí no se ve como el código Python válido (y no ejecuta en mi sesión intérprete

representación que en:.

myarr = { 
    'Richard': 1, 
    'Reuben': 4, 
    'Reece': 0, 
    'Rohan': 3, 
    'Ralph': 2, 
    'Raphael': 0, 
    'Robin': 0 } 

me da algo en lo que yo podría basar una respuesta.

la forma recomendada de hacer la clasificación personalizada en Python es usar ESD (decorar, clasificar, Undecorate) patrón. Si desea ordenar un diccionario por los valores a continuación, que se ve algo como:

keys_sorted_by_val = [ x[1] for x in sorted([(v,k) for k,v in myarr.items()])] 

...donde (v,k) for k,v in myarr.items() es la expresión decorar; sorted() es, obviamente, el tipo y el x[1] for x in ... externo es el final 0coco paso.

Obviamente esto puede parecer un requisito bastante común que una fuerza, la cual terminar con esto en una función:

def dict_by_values(d): 
    return [ x[1] for x in sorted([(v,k) for k,v in d.items()])] 

Si tuviera una colección de instancias de objetos que desea ordenar por algún atributo que pueda usar algo como esto:

def sort_by_attr(attr, coll): 
    results = list() 
    for each in coll: 
     assert hasattr(each, attr) 
     results.append((getattr(each, attr), each)) 
    results.sort() 
    return [x[1] for x in results] 

Así que si creamos una clase que representa su nombre/datos de rango como este:

class NameRanking(object): 
    def __init__(self, name, rank): 
     self.name = name 
     self.rank = rank 
    def __repr__(self): 
     return "%s: %s, %s" %(self.__class__, self.name, self.rank) 

... e instanciar una lista de los que utilizan myarr:

name_rankings = [(k, v) NameRanking para k, v en myarr.items()]

... entonces podríamos obtener una copia ordenada de que el uso de:

names_rankings_by_rank = sort_by_attr('rank', name_rankings) 

(Sí, el assert no es una buena idea aquí; ahí es donde pondría su propio manejo de excepción o código de lanzamiento según corresponda a su aplicación).

Cuestiones relacionadas