Python сортировка списка словарей по нескольким ключам
у меня есть список предсказывает:
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}]
и мне нужно использовать сортировку с несколькими ключами, перевернутую Total_Points, а затем не перевернутую TOT_PTS_Misc
.
Это можно сделать в командной строке вот так:
a = sorted(b, key=lambda d: (-d['Total_Points'], d['TOT_PTS_Misc']))
но я должен запустить это через функцию,где я передаю в список и ключи сортировки. Например, def multikeysort(dict_list, sortkeys):
.
как можно использовать лямбда-строку, которая будет сортировать список, для произвольного количества ключей, которые передаются в функция multikeysort, и принять во внимание, что sortkeys может иметь любое количество ключей, а те, которые нуждаются в обратных сортировках, будут идентифицированы с " - " перед ним?
8 ответов:
этот ответ работает для любого столбца в словаре - отрицаемый столбец не должен быть числом.
def multikeysort(items, columns): from operator import itemgetter comparers = [((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns] def comparer(left, right): for fn, mult in comparers: result = cmp(fn(left), fn(right)) if result: return mult * result else: return 0 return sorted(items, cmp=comparer)
вы можете назвать это так:
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}] a = multikeysort(b, ['-Total_Points', 'TOT_PTS_Misc']) for item in a: print item
попробуйте это с любым столбцом отрицается. Вы увидите обратный порядок сортировки.
далее: измените его, чтобы он не использовал дополнительный класс....
2016-01-17
черпая вдохновение из этого ответа что является лучшим способом, чтобы получить первый элемент из итерируемый соответствие условию?, я сократил код:
from operator import itemgetter as i def multikeysort(items, columns): comparers = [ ((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1)) for col in columns ] def comparer(left, right): comparer_iter = ( cmp(fn(left), fn(right)) * mult for fn, mult in comparers ) return next((result for result in comparer_iter if result), 0) return sorted(items, cmp=comparer)
в случае, если вам нравится ваш лаконичный код.
позже 2016-01-17
это работает с python3 (который устранил до
sort
):from operator import itemgetter as i from functools import cmp_to_key def multikeysort(items, columns): comparers = [ ((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1)) for col in columns ] def comparer(left, right): comparer_iter = ( cmp(fn(left), fn(right)) * mult for fn, mult in comparers ) return next((result for result in comparer_iter if result), 0) return sorted(items, key=cmp_to_key(comparer))
вдохновленный этим ответом как я должен сделать пользовательскую сортировку в Python 3?
Я знаю, что это довольно старый вопрос, но ни один из ответов не упоминает, что Python гарантирует стабильный порядок сортировки для своих процедур сортировки, таких как
list.sort()
иsorted()
, что означает, что элементы, которые равны сохраняют свой первоначальный заказ.Это означает, что эквивалентно
ORDER BY name ASC, age DESC
(используя нотацию SQL) для списка словарей можно сделать так:items.sort(key=operator.itemgetter('age'), reverse=True) items.sort(key=operator.itemgetter('name'))
реверсирование / инвертирование работает для всех упорядоченных типов, а не только для чисел, которые вы можете отрицать ставим знак минус впереди.
и из-за алгоритма Timsort, используемого в (по крайней мере) CPython, это на самом деле довольно быстро на практике.
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']))
http://stygianvision.net/updates/python-sort-list-object-dictionary-multiple-key/ имеет хорошее краткое изложение различных методов для этого. Если ваши требования проще ,чем" полный двунаправленный мультикей", взгляните. Ясно, что принятый ответ и сообщение в блоге я просто ссылки каким-то образом повлияли друг на друга, хотя я не знаю, в каком порядке.
в случае, если ссылка умрет, вот очень краткий обзор примеров, не охваченных выше:
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']))
Я использую следующее для сортировки 2d массива на несколько столбцов
def k(a,b): def _k(item): return (item[a],item[b]) return _k
Это может быть расширены для работы с произвольным количеством элементов. Я склонен думать, что найти лучший шаблон доступа к вашим сортируемым ключам лучше, чем писать причудливый компаратор.
>>> 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]]
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)
демонстрация:
>>> 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'}]
разбор немного хрупкий, но, по крайней мере, он допускает переменное количество пробелов между ключами.
Так как вы уже знакомы с лямбда, вот менее подробное решение.
>>> 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)
сегодня у меня была аналогичная проблема - пришлось сортировать элементы справочника по убыванию числовых значений и по возрастанию строковых значений. Чтобы решить проблему конфликтующих направлений, я отрицал целочисленные значения.
вот вариант моего решения-применительно к OP
sorted(b, key=lambda e: (-e['Total_Points'], e['TOT_PTS_Misc']))
очень просто - и работает как шарм
[{'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}]