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

时间:2012-04-12 18:30:18

标签: 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这样的东西。

8 个答案:

答案 0 :(得分:23)

选项1:

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

选项2:

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

演示:

  

“我想按等级值对其进行排序,排序如下:1-2-3-4-0-0-0。” - 原始海报

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

补充意见:

  

“请你解释一下(Python新手)它在做什么?我可以看到它是一个lambda,我知道它是一个匿名函数:括号中的位是什么?” - OP评论

索引/切片表示法

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= vs cmp= vs富裕比较

至于发生了什么,有两种常用方法可以指定排序算法的工作方式:一种是key函数,另一种是cmp函数(现在不推荐使用) python,但更多功能)。虽然cmp函数允许您任意指定两个元素应如何比较(输入:ab;输出:a<ba>b或{{1 }})。虽然合法但它没有给我们带来任何重大好处(我们必须以笨拙的方式复制代码),并且关键功能更适合您的情况。 (有关如何以优雅但可能过度的方式隐式定义a==b,请参阅“对象丰富的比较”。)

实施关键功能

不幸的是,0是整数的元素,因此具有自然顺序:0通常&lt; 1,2,3 ......因此,如果我们想要施加额外的规则,我们需要将列表排在“更高级别”。我们通过使键成为元组来实现这一点:元组首先按其第一个元素排序,然后按第二个元素排序。真假将永远在False之后订购,所以所有的Trues将在Falses之后订购;然后他们将按正常排序:cmp=(True,1)<(True,2)<(True,3)<...(False,1)<(False,2)<...。替代方案(选项2),仅为rank-0字典指定无穷大的值,因为这保证高于任何可能的等级。

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

更通用的解决方案是创建一个表示记录的类,然后实现(False,*)<(True,*)__lt____gt____eq____ne__,{ {1}}以及所有其他rich comparison operators,或者只是实施其中一个__gt__并使用@functools.total_ordering decorator。每当您使用比较运算符时,这将导致该类的对象使用自定义逻辑(例如__ge__ __eq__ x=Record(name='Joe', rank=12));由于y=Record(...)函数在比较排序中默认使用x<y和其他比较运算符,这将使排序时自动行为,并且在其他情况下使用sorted(...)和其他比较运算符。根据您的使用情况,这可能会或可能不会过多。

更清洁的替代 - 不要使用语义重载0:

然而,我应该指出,将0s放在1,2,3,4等之后有点人为。这是否合理取决于rank = 0是否真的意味着rank = 0;如果rank = 0真的“低于”rank = 1(其实际上“低于”rank = 2 ......)。如果确实如此,那么你的方法就完全没问题了。如果不是这种情况,那么您可以考虑省略<条目而不是设置<。然后你可以使用'rank':...或者通过:

按Lev Levitsky的答案排序

选项1使用不同的方案:

'rank':0

选项2采用不同的方案:

'rank' in d

旁注:依赖于python中无穷大的存在几乎是一个黑客的边界,制作任何提到的解决方案(元组,对象比较),Lev的filter-then-concatenate solution,甚至可能稍微更多 - 复杂cmp solution(由威尔逊打字),更适用于其他语言。

答案 1 :(得分:1)

我会做

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

我觉得它可以以某种方式压缩。

答案 2 :(得分:1)

我更倾向于创建一个比较函数来专门处理“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'])

但是,自定义比较功能会有性能损失。

答案 3 :(得分:-1)

一种愚蠢的方法是:

sorted_master_list = sorted(myarr, key=lambda x: 99999 if x['rank'] == 0 else x['rank'])

如果你知道自己的最高等级,那么效果会很好。

答案 4 :(得分:-1)

你这里的myarr绑定看起来不像有效的Python代码(并且不会在我的解释器会话中执行。

将其呈现为:

myarr = {
    'Richard': 1,
    'Reuben': 4,
    'Reece': 0,
    'Rohan': 3,
    'Ralph': 2,
    'Raphael': 0,
    'Robin': 0 }

给我一​​些我可以基于答案的东西。

在Python中进行自定义排序的推荐方法是使用DSU(装饰,排序,未装饰)模式。 如果要按值对字典进行排序,则类似于:

keys_sorted_by_val = [ x[1] for x in sorted([(v,k) for k,v in myarr.items()])]

...其中(v,k) for k,v in myarr.items()装饰的表达式; sorted()显然是排序,而外x[1] for x in ...是最终的 undecorate 步骤。

显然,这似乎是一个足够普遍的要求,可以将其包装在一个函数中:

def dict_by_values(d):
    return [ x[1] for x in sorted([(v,k) for k,v in d.items()])]

如果您有一组要按某个属性排序的对象实例,可以使用以下内容:

def sort_by_attr(attr, coll):
    results = list()
    for each in coll:
        assert hasattr(each, attr)
        results.append((getattr(each, attr), each))
    results.sort()
    return [x[1] for x in results]

因此,如果我们创建了一个代表你的名字/等级数据的类:

class NameRanking(object):
    def __init__(self, name, rank):
        self.name = name
        self.rank = rank
    def __repr__(self):
        return "%s: %s, %s" %(self.__class__, self.name, self.rank)

...并使用myarr实例化一个列表:

name_rankings = [姓名为k(v)为k,v为myarr.items()]

...然后我们可以使用以下方式获取该副本:

names_rankings_by_rank = sort_by_attr('rank', name_rankings)

(是的,assert在这里不是一个好主意;那就是你将自己的异常处理或代码投入到你的应用程序中的地方。)

答案 5 :(得分:-2)

只需传递给“key”任意函数或可调用对象 - 它是需要的。 itemgetter碰巧是一个这样的功能 - 但它可以工作 使用您编写的任何函数 - 它只需要将一个参数作为输入,然后返回 一个直接可以达到你想要的顺序的对象。

在这种情况下:

def key_func(item):
   return item["rank"] if item["rank"] != 0 else -100000

sorted_master_list = sorted(myarr, key=key_func)

(它也可以写成lambda表达式)

答案 6 :(得分:-3)

您可以在密钥参数中使用函数:

用于屁股排序:

sorted_master_list = sorted(myarr, key=lambda x: x.get('rank'))

或desc:

sorted_master_list = sorted(myarr, key=lambda x: -x.get('rank'))

此外,您可以在此处阅读有关排序函数http://wiki.python.org/moin/HowTo/Sorting

答案 7 :(得分:-3)

试 sorted_master_list =已排序(myarr,key = itemgetter('rank'),reverse = True)