10

我有以下 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的东西。

4

8 回答 8

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

[...]被称为索引/切片表示法,请参阅解释 Python 的切片表示法- 另请注意,这someArray[n]是许多编程语言中用于索引的常用表示法,但可能不支持形式的切片[start:end][start:end:step].

key=vs cmp=vs 丰富的比较

至于发生了什么,有两种常见的方法来指定排序算法的工作方式:一种是使用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__和所有其他丰富的比较运算符,或者只实现其中一个__eq__并使用@functools.total_ordering装饰器。这将导致该类的对象在您使用比较运算符(例如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=2 ...)。如果情况确实如此,那么您的方法非常好。如果不是这种情况,那么您可能会考虑省略'rank':...条目而不是设置'rank':0。然后你可以使用 Lev Levitsky 的答案排序'rank' in d,或者:

具有不同方案的选项1:

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

具有不同方案的选项2:

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

旁注:依靠 python 中无穷大的存在几乎是一种 hack,使任何提到的解决方案(元组、对象比较)、Lev 的filter-then-concatenate 解决方案,甚至可能稍微复杂的cmp解决方案(键入wilson),更普遍适用于其他语言。

于 2012-04-12T18:34:07.603 回答
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']]

我想它可能会以某种方式被压缩。

于 2012-04-12T18:38:41.253 回答
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'])

但是,自定义比较功能会降低性能。

于 2012-04-12T19:13:38.643 回答
-1

一个hacky方法是:

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

如果您知道自己的最高等级,这将非常有效。

于 2012-04-12T18:34:09.383 回答
-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 ...

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

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 = [ nameRanking(k, v) for k,v in myarr.items() ]

...然后我们可以使用以下方法获得该副本的排序副本:

names_rankings_by_rank = sort_by_attr('rank', name_rankings)

(是的,assert这不是一个好主意;那是您可以根据自己的应用程序放入自己的异常处理或抛出代码的地方)。

于 2012-04-12T19:30:26.097 回答
-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 表达式)

于 2012-04-12T18:33:52.847 回答
-3

您可以在关键参数中使用函数:

屁股排序:

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

或描述:

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

您也可以在此处阅读有关排序功能的信息http://wiki.python.org/moin/HowTo/Sorting

于 2012-04-12T18:34:02.867 回答
-3

尝试 sorted_master_list = sorted(myarr, key=itemgetter('rank'), reverse=True)

于 2012-04-12T18:36:15.987 回答