Python:使用自定义比较器对字典数组进行排序?

2024-04-27 19:30:51 发布

您现在位置:Python中文网/ 问答频道 /正文

我有以下Python字典数组:

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 } ]

我想按等级值排序,顺序如下:1-2-3-4-0-0-0。

如果我尝试:

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

然后按0-0-0-1-2-3-4的顺序对列表进行排序。

如何定义一个自定义比较器函数,将零推到列表的底部?我在想我能不能用点类似methodcaller的东西。


Tags: namerichard列表字典排序顺序数组sorted
3条回答

选项1:

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

选择2:

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

演示:

"I'd like to sort it by the rank values, ordering as follows: 1-2-3-4-0-0-0." --original poster

>>> 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]

其他评论:

"Please could you explain to me (a Python novice) what it's doing? I can see that it's a lambda, which I know is an anonymous function: what's the bit in brackets?" – OP comment

索引/切片表示法:

itemgetter('rank')lambda x: x['rank']是相同的,与函数是相同的:

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

[...]称为索引/切片表示法,请参见Explain Python's slice notation-还要注意someArray[n]是许多编程语言中用于索引的常用表示法,但可能不支持[start:end][start:end:step]形式的切片。

key=cmp=与丰富比较:

至于发生了什么,有两种常用的方法来指定排序算法是如何工作的:一种是使用key函数,另一种是使用cmp函数(现在在python中已弃用,但用途更广)。而cmp函数允许您任意指定两个元素应该如何比较(输入:ab;输出:a<ba>ba==b)。虽然是合法的,但它并没有给我们带来任何好处(我们不得不以一种尴尬的方式复制代码),而且对于您的情况,键函数更自然。(有关如何以优雅但可能过分的方式隐式定义cmp=,请参阅“对象丰富比较”。)

实现关键功能:

不幸的是,0是整数的一个元素,因此具有自然顺序:0通常是<;1,2,3。。。因此,如果我们想强加一个额外的规则,我们需要在“更高的层次”对列表进行排序。为此,我们将键设为元组:元组首先按其第一个元素排序,然后按其第二个元素排序。True总是在False之后排序,因此所有True都将在False之后排序;然后它们将按正常顺序排序:(True,1)<(True,2)<(True,3)<...(False,1)<(False,2)<...(False,*)<(True,*)。另一种选择(选项2)仅仅为rank-0字典分配一个无穷大的值,因为它保证高于任何可能的秩。

更一般的替代方案-对象丰富的比较:

更一般的解决方案是创建一个表示记录的类,然后实现__lt____gt____eq____ne____gt____ge__和所有其他rich comparison operators,或者只实现其中一个和__eq__,并使用^{} decorator。这将导致该类的对象在使用比较运算符(例如x=Record(name='Joe', rank=12)y=Record(...)x<y)时使用自定义逻辑;由于sorted(...)函数在比较排序中默认使用<和其他比较运算符,这将使排序时以及在使用<和其他比较运算符。这可能是过度的,也可能不是过度的,这取决于您的用例。

更干净的替代方案-不要用语义重载0:

不过,我应该指出,把0放在1、2、3、4等后面有点人为。这是否合理取决于rank=0是否真的意味着rank=0;如果rank=0真的比rank=1“低”(而rank=1又真的比rank=2“低”)。如果这是真的,那么你的方法是完美的。如果不是这样,那么可以考虑省略'rank':...条目,而不是设置'rank':0。然后您可以使用'rank' in d按Lev Levitsky的答案排序,或者按:

不同方案的方案1:

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

不同方案的方案2:

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

侧注:依赖于python中无穷大的存在几乎是一种技巧,使得任何提到的解决方案(元组、对象比较)、Lev的filter-then-concatenate solution,甚至可能是稍微复杂一点的^{} solution(由wilson键入),对其他语言更具通用性。

我更倾向于创建一个比较函数来具体处理“0”:

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'])

但是,自定义比较函数有性能损失。

我会的

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

我想它可能被压缩了。

相关问题 更多 >