O, prácticamente, ¿cómo puedo ordenar una lista de diccionarios por múltiples claves?
Tengo una lista de dictados:
b = [{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0},
{u'TOT_PTS_Misc': u'Foster, Toney', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Lawson, Roman', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Lempke, Sam', u'Total_Points': 80.0},
{u'TOT_PTS_Misc': u'Gnezda, Alex', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Kirks, Damien', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Worden, Tom', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Korecz, Mike', u'Total_Points': 78.0},
{u'TOT_PTS_Misc': u'Swartz, Brian', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Burgess, Randy', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Smugala, Ryan', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Harmon, Gary', u'Total_Points': 66.0},
{u'TOT_PTS_Misc': u'Blasinsky, Scott', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Carter III, Laymon', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Coleman, Johnathan', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Venditti, Nick', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Blackwell, Devon', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Kovach, Alex', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Bolden, Antonio', u'Total_Points': 60.0},
{u'TOT_PTS_Misc': u'Smith, Ryan', u'Total_Points': 60.0}]
y necesito usar un tipo de clave múltiple invertido por Total_Points, luego no invertido por TOT_PTS_Misc
.
Esto se puede hacer en el símbolo del sistema así:
a = sorted(b, key=lambda d: (-d['Total_Points'], d['TOT_PTS_Misc']))
Pero tengo que ejecutar esto a través de una función, donde paso la lista y las claves de clasificación. Por ejemplo def multikeysort(dict_list, sortkeys):
,.
¿Cómo se puede usar la línea lambda que ordenará la lista, para un número arbitrario de claves que se pasan a la función multikeysort, y tener en cuenta que las sortkeys pueden tener cualquier número de claves y se identificarán aquellas que necesitan ordenaciones inversas? con un '-' antes?
cmp()
no está disponible para Python3, así que tuve que definirlo yo mismo, como se menciona aquí: stackoverflow.com/a/22490617/398514cmp
palabra clave, pero lacmp()
función todavía se usa 4 líneas arriba. Lo probé con 3.2, 3.3, 3.4 y 3.5, todos fallaron en la llamada a la función, porquecmp()
no está definida. La tercera viñeta aquí ( docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons ) menciona el tratamientocmp()
como desaparecido.Este artículo tiene un buen resumen de varias técnicas para hacer esto. Si sus requisitos son más simples que "multicaídas bidireccionales completas", eche un vistazo. Está claro que la respuesta aceptada y la publicación de blog a la que acabo de hacer referencia se influyeron mutuamente de alguna manera, aunque no sé en qué orden.
En caso de que el enlace muera, aquí hay una sinopsis muy rápida de ejemplos no cubiertos anteriormente:
mylist = sorted(mylist, key=itemgetter('name', 'age')) mylist = sorted(mylist, key=lambda k: (k['name'].lower(), k['age'])) mylist = sorted(mylist, key=lambda k: (k['name'].lower(), -k['age']))
fuente
result = cmp(fn(left), fn(right))
Sé que esta es una pregunta bastante antigua, pero ninguna de las respuestas menciona que Python garantiza un orden de clasificación estable para sus rutinas de clasificación, como
list.sort()
ysorted()
, lo que significa que los elementos que se comparan igual conservan su orden original.Esto significa que el equivalente de
ORDER BY name ASC, age DESC
(usando notación SQL) para una lista de diccionarios se puede hacer así:items.sort(key=operator.itemgetter('age'), reverse=True) items.sort(key=operator.itemgetter('name'))
Observe cómo los elementos se ordenan primero por el atributo "menor"
age
(descendente), luego por el atributo "mayor"name
, lo que lleva al orden final correcto.La inversión / inversión funciona para todos los tipos que se pueden solicitar, no solo los números que puede negar poniendo un signo menos al frente.
Y debido al algoritmo Timsort usado en (al menos) CPython, esto es bastante rápido en la práctica.
fuente
def sortkeypicker(keynames): negate = set() for i, k in enumerate(keynames): if k[:1] == '-': keynames[i] = k[1:] negate.add(k[1:]) def getit(adict): composite = [adict[k] for k in keynames] for i, (k, v) in enumerate(zip(keynames, composite)): if k in negate: composite[i] = -v return composite return getit a = sorted(b, key=sortkeypicker(['-Total_Points', 'TOT_PTS_Misc']))
fuente
some_string.split(",")
Utilizo lo siguiente para ordenar una matriz 2d en varias columnas
def k(a,b): def _k(item): return (item[a],item[b]) return _k
Esto podría ampliarse para trabajar en un número arbitrario de elementos. Tiendo a pensar que encontrar un mejor patrón de acceso a sus claves ordenables es mejor que escribir un comparador elegante.
>>> data = [[0,1,2,3,4],[0,2,3,4,5],[1,0,2,3,4]] >>> sorted(data, key=k(0,1)) [[0, 1, 2, 3, 4], [0, 2, 3, 4, 5], [1, 0, 2, 3, 4]] >>> sorted(data, key=k(1,0)) [[1, 0, 2, 3, 4], [0, 1, 2, 3, 4], [0, 2, 3, 4, 5]] >>> sorted(a, key=k(2,0)) [[0, 1, 2, 3, 4], [1, 0, 2, 3, 4], [0, 2, 3, 4, 5]]
fuente
Tuve un problema similar hoy: tuve que ordenar los elementos del diccionario por valores numéricos descendentes y valores de cadena ascendentes. Para resolver el problema de direcciones conflictivas, negué los valores enteros.
Aquí hay una variante de mi solución, según corresponda a OP
sorted(b, key=lambda e: (-e['Total_Points'], e['TOT_PTS_Misc']))
Muy simple y funciona a las mil maravillas.
[{'TOT_PTS_Misc': 'Chappell, Justin', 'Total_Points': 96.0}, {'TOT_PTS_Misc': 'Russo, Brandon', 'Total_Points': 96.0}, {'TOT_PTS_Misc': 'Utley, Alex', 'Total_Points': 96.0}, {'TOT_PTS_Misc': 'Foster, Toney', 'Total_Points': 80.0}, {'TOT_PTS_Misc': 'Lawson, Roman', 'Total_Points': 80.0}, {'TOT_PTS_Misc': 'Lempke, Sam', 'Total_Points': 80.0}, {'TOT_PTS_Misc': 'Gnezda, Alex', 'Total_Points': 78.0}, {'TOT_PTS_Misc': 'Kirks, Damien', 'Total_Points': 78.0}, {'TOT_PTS_Misc': 'Korecz, Mike', 'Total_Points': 78.0}, {'TOT_PTS_Misc': 'Worden, Tom', 'Total_Points': 78.0}, {'TOT_PTS_Misc': 'Burgess, Randy', 'Total_Points': 66.0}, {'TOT_PTS_Misc': 'Harmon, Gary', 'Total_Points': 66.0}, {'TOT_PTS_Misc': 'Smugala, Ryan', 'Total_Points': 66.0}, {'TOT_PTS_Misc': 'Swartz, Brian', 'Total_Points': 66.0}, {'TOT_PTS_Misc': 'Blackwell, Devon', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Blasinsky, Scott', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Bolden, Antonio', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Carter III, Laymon', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Coleman, Johnathan', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Kovach, Alex', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Smith, Ryan', 'Total_Points': 60.0}, {'TOT_PTS_Misc': 'Venditti, Nick', 'Total_Points': 60.0}]
fuente
from operator import itemgetter from functools import partial def _neg_itemgetter(key, d): return -d[key] def key_getter(key_expr): keys = key_expr.split(",") getters = [] for k in keys: k = k.strip() if k.startswith("-"): getters.append(partial(_neg_itemgetter, k[1:])) else: getters.append(itemgetter(k)) def keyfunc(dct): return [kg(dct) for kg in getters] return keyfunc def multikeysort(dict_list, sortkeys): return sorted(dict_list, key = key_getter(sortkeys)
Demostración:
>>> multikeysort([{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 60.0}, {u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0}, {u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0}], "-Total_Points,TOT_PTS_Misc") [{u'Total_Points': 96.0, u'TOT_PTS_Misc': u'Chappell, Justin'}, {u'Total_Points': 96.0, u'TOT_PTS_Misc': u'Russo, Brandon'}, {u'Total_Points': 60.0, u'TOT_PTS_Misc': u'Utley, Alex'}]
El análisis es un poco frágil, pero al menos permite un número variable de espacios entre las claves.
fuente
Como ya se siente cómodo con lambda, aquí tiene una solución menos detallada.
>>> def itemgetter(*names): return lambda mapping: tuple(-mapping[name[1:]] if name.startswith('-') else mapping[name] for name in names) >>> itemgetter('a', '-b')({'a': 1, 'b': 2}) (1, -2)
fuente