Lub, praktycznie, jak mogę posortować listę słowników według wielu kluczy?
Mam listę dyktand:
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}]
i muszę użyć sortowania z wieloma klawiszami odwróconymi przez Total_Points, a następnie nie odwróconych przez TOT_PTS_Misc
.
Można to zrobić w wierszu polecenia w następujący sposób:
a = sorted(b, key=lambda d: (-d['Total_Points'], d['TOT_PTS_Misc']))
Ale muszę to uruchomić za pomocą funkcji, w której przekazuję listę i klucze sortowania. Na przykład def multikeysort(dict_list, sortkeys):
.
W jaki sposób można użyć wiersza lambda, który posortuje listę dla dowolnej liczby kluczy, które są przekazywane do funkcji multikeysort, i wziąć pod uwagę, że sortkeys mogą mieć dowolną liczbę kluczy, a te, które wymagają odwróconych sortowań, zostaną zidentyfikowane z „-” przed nim?
cmp()
nie jest dostępny dla Pythona3, więc musiałem go zdefiniować samodzielnie, jak wspomniano tutaj: stackoverflow.com/a/22490617/398514cmp
słowo kluczowe, alecmp()
funkcja jest nadal używana 4 wiersze powyżej. Wypróbowałem z 3.2, 3.3, 3.4 i 3.5, wszystkie zawiodły przy wywołaniu funkcji, ponieważcmp()
nie są zdefiniowane. Trzeci punkt tutaj ( docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons ) wspomina o traktowaniucmp()
jako zniknęło.Ten artykuł zawiera niezłe podsumowanie różnych technik, aby to zrobić. Jeśli Twoje wymagania są prostsze niż „pełne dwukierunkowe wielokierunkowe”, przyjrzyj się. Jest jasne, że zaakceptowana odpowiedź i wpis na blogu, do którego właśnie się odwołałem, wpłynęły na siebie w jakiś sposób, chociaż nie wiem, w jakiej kolejności.
W przypadku, gdy link zniknie, oto bardzo krótkie streszczenie przykładów nieuwzględnionych powyżej:
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']))
źródło
result = cmp(fn(left), fn(right))
Wiem, że jest to dość stare pytanie, ale żadna z odpowiedzi nie wspomina, że Python gwarantuje stabilny porządek sortowania dla swoich procedur sortowania, takich jak
list.sort()
isorted()
, co oznacza, że elementy, które porównują równe, zachowują swoją pierwotną kolejność.Oznacza to, że odpowiednik
ORDER BY name ASC, age DESC
(przy użyciu notacji SQL) dla listy słowników można zrobić w następujący sposób:items.sort(key=operator.itemgetter('age'), reverse=True) items.sort(key=operator.itemgetter('name'))
Zwróć uwagę, jak pozycje są najpierw sortowane według atrybutu „mniejszy”
age
(malejąco), a następnie według atrybutu „major”name
, co prowadzi do właściwej ostatecznej kolejności.Odwrócenie / odwrócenie działa dla wszystkich możliwych do uporządkowania typów, a nie tylko liczb, które można zanegować, umieszczając na początku znak minus.
A ze względu na algorytm Timsort używany w (przynajmniej) CPythonie, w praktyce jest to dość szybkie.
źródło
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']))
źródło
some_string.split(",")
Używam poniższego do sortowania tablicy 2d na wielu kolumnach
def k(a,b): def _k(item): return (item[a],item[b]) return _k
Można to rozszerzyć na dowolną liczbę pozycji. Wydaje mi się, że znalezienie lepszego wzoru dostępu do kluczy, które można sortować, jest lepsze niż pisanie fantazyjnego komparatora.
>>> 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]]
źródło
Miałem dziś podobny problem - musiałem sortować elementy słownika według malejących wartości liczbowych i rosnących wartości ciągów. Aby rozwiązać problem sprzecznych kierunków, zanegowałem wartości całkowite.
Oto wariant mojego rozwiązania - w przypadku OP
sorted(b, key=lambda e: (-e['Total_Points'], e['TOT_PTS_Misc']))
Bardzo proste - i działa jak urok
[{'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}]
źródło
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)
Demonstracja:
>>> 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'}]
Parsowanie jest nieco kruche, ale przynajmniej pozwala na zmienną liczbę spacji między kluczami.
źródło
Ponieważ już dobrze znasz lambdę, oto mniej rozwlekłe rozwiązanie.
>>> 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)
źródło