如何按多个键对对象进行排序?
- 2025-02-28 08:23:00
- admin 原创
- 70
问题描述:
或者,实际上,我如何按多个键对字典列表进行排序?
我有一份口述清单:
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 函数的任意数量的键,如何使用 lambda 行对列表进行排序,并考虑到 sortkeys 可能有任意数量的键,而那些需要反向排序的键将在其前面用“-”标识?
解决方案 1:
这篇文章对实现此目的的各种技术进行了很好的概述。如果您的要求比“完全双向多密钥”更简单,请看一下。很明显,接受的答案和我刚刚引用的博客文章在某种程度上相互影响,尽管我不知道是哪种顺序。
如果链接失效,下面是上面未涉及的示例的简要概述:
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']))
解决方案 2:
这个答案适用于字典中的任何类型的列——否定的列不必是数字。
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 (消除了 的cmp
参数sort
):
from operator import itemgetter as i
from functools import cmp_to_key
def cmp(x, y):
"""
Replacement for built-in function cmp that was removed in Python 3
Compare the two objects x and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
https://portingguide.readthedocs.io/en/latest/comparisons.html#the-cmp-function
"""
return (x > y) - (x < y)
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 中进行自定义排序?
解决方案 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'))
请注意,这些项目首先按“较小”属性(降序)排序age
,然后按“主要”属性排序name
,从而得到正确的最终顺序。
反转/倒置适用于所有可排序类型,而不仅仅是可以通过在前面加上减号来取反的数字。
并且由于 (至少) CPython 中使用了 Timsort 算法,因此在实践中这实际上相当快。
解决方案 4:
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']))
解决方案 5:
我今天遇到了类似的问题 - 我必须按降序排列数字值,按升序排列字符串值来对字典项进行排序。为了解决方向冲突的问题,我对整数值取反。
这是我的解决方案的一个变体 - 适用于 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}]
解决方案 6:
我使用以下内容对多列上的二维数组进行排序
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]]
解决方案 7:
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'}]
解析有点脆弱,但至少它允许键之间存在可变数量的空格。
解决方案 8:
由于您已经熟悉 lambda,这里有一个不太冗长的解决方案。
>>> 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)
解决方案 9:
这里有一个简单的函数,可以按照帖子的要求与“任意数量的键”一起使用,并且可以通过在键名称前面添加“-”来反转任意数量的列:
b = [
{"TOT_PTS_Misc": "Utley, Alex", "Total_Points": 96.0},
{"TOT_PTS_Misc": "Russo, Brandon", "Total_Points": 96.0},
{"TOT_PTS_Misc": "Chappell, Justin", "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": "Worden, Tom", "Total_Points": 78.0},
{"TOT_PTS_Misc": "Korecz, Mike", "Total_Points": 78.0},
{"TOT_PTS_Misc": "Swartz, Brian", "Total_Points": 66.0},
{"TOT_PTS_Misc": "Burgess, Randy", "Total_Points": 66.0},
{"TOT_PTS_Misc": "Smugala, Ryan", "Total_Points": 66.0},
{"TOT_PTS_Misc": "Harmon, Gary", "Total_Points": 66.0},
{"TOT_PTS_Misc": "Blasinsky, Scott", "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": "Venditti, Nick", "Total_Points": 60.0},
{"TOT_PTS_Misc": "Blackwell, Devon", "Total_Points": 60.0},
{"TOT_PTS_Misc": "Kovach, Alex", "Total_Points": 60.0},
{"TOT_PTS_Misc": "Bolden, Antonio", "Total_Points": 60.0},
{"TOT_PTS_Misc": "Smith, Ryan", "Total_Points": 60.0},
]
def multi_key_sort(dict_list, sort_keys):
for sort_key in reversed(sort_keys):
dict_list = sorted(dict_list, key=lambda d: -d[sort_key[1:]] if sort_key.startswith("-") else d[sort_key])
return dict_list
c = ["-Total_Points", "TOT_PTS_Misc"]
a = multi_key_sort(b, c)
for d in a:
print(d)
如果您不需要对任何列进行反向排序,则更简单的函数是:
def multi_key_sort(dict_list, sort_keys):
return sorted(dict_list, key=lambda d: tuple(d[sort_key] for sort_key in sort_keys))
扫码咨询,免费领取项目管理大礼包!