如何为降序值编写Python排序键函数

时间:2012-06-26 12:00:52

标签: python sorting

最新版本的Python中将 key 函数从前一个 cmp 函数传递给sort()的举动让我更难以执行复杂的排序在某些物体上。

例如,我想要从最新到最旧的一组对象排序,并带有一组字符串tie-breaker字段。所以我希望日期顺序相反,但字符串按其自然顺序排列。使用比较函数,我可以反转日期字段与字符串字段的比较。但是通过关键功能,我需要找到一些方法来反转/反转日期或字符串。

与数字一样容易(虽然难看) - 只是从某些东西中减去它们 - 但我是否必须找到类似的日期(从另一个日期减去它们并比较timedeltas?)和字符串() ......我不知道我是如何以与语言环境无关的方式改变他们的顺序的。)

我知道functools.cmp_to_key()的存在,但它被描述为"主要用作转换为Python 3的程序的转换工具,其中不再支持比较函数" 。这意味着我应该能够用关键方法做我想做的事 - 但是怎么做?

7 个答案:

答案 0 :(得分:22)

执行此操作的最通用方法是依次按每个键分别排序。 Python的排序总是稳定的,所以这样做是安全的:

sort(data, key=tiebreakerkey)
sort(data, key=datekey, reverse=True)

将(假设关键函数的相关定义)为您提供按降序日期和升序破坏者排序的数据。

请注意,这样做比生成单个复合键函数要慢,因为您最终会完成两个完整的排序,因此如果您可以生成更好的复合键,但将其拆分为单独的排序会产生一个很多灵活性:给定每个列的关键功能,您可以对它们进行任意组合,并为任何单个列指定反向。

对于完全通用的选项:

keys = [ (datekey, True), (tiebreakerkey, False) ]
for key, rev in reversed(keys):
    sort(data, key=key, reverse=rev)

并且为了完整性,尽管我认为应该尽可能避免:

from functools import cmp_to_key
sort(data, key=cmp_to_key(your_old_comparison_function))

我认为你应该避免这种情况的原因是你回到对比较函数进行n log n次调用时与n调用关键函数(或2n调用排序两次)。

答案 1 :(得分:12)

这种缓慢而优雅的方法是创建一个反转顺序的值包装器:

from functools import total_ordering
@total_ordering
class ReversedOrder:
    def __init__(self, value):
        self.value = value
    def __eq__(self, other):
        return other.value == self.value
    def __lt__(self, other):
        return other.value < self.value

如果您没有functools.total_ordering,则必须执行所有6次比较,例如:

import operator
class ReversedOrder:
    def __init__(self, value):
        self.value = value
for x in ['__lt__', '__le__', '__eq__', '__ne__', '__ge__', '__gt__']:
    op = getattr(operator, x)
    setattr(ReversedOrder, x, lambda self, other, op=op: op(other.value, self.value))

答案 2 :(得分:11)

我认为文档不完整。我将“主要”这个词解释为仍然有理由使用cmp_to_key,这就是其中之一。 cmp被删除了,因为这是一个“有吸引力的滋扰:”人们会倾向于它,即使key是更好的选择。

但是您的案例显然更好cmp函数,因此请使用cmp_to_key来实现它。

答案 3 :(得分:4)

排序两次,每次按键一次,然后反转。

(Python sortstable;也就是说,除非必须,否则它不会更改原始列表的顺序。)

如果您关心如何排序相等的元素,那么确实对您进行排序的顺序很重要。

答案 4 :(得分:1)

一种方法是使用pandas库和参数ascending,通过执行以下操作来设置要排序的列和要降序的列: ascending=[True,False,False]

您不仅可以对 两个 级别(例如datetimestr)进行此操作,还可以对 任何< / em> 所需的级别数。

例如,如果您有

d = [[1, 2, datetime(2017,1,2)], 
     [2, 2, datetime(2017,1,4)],
     [2, 3, datetime(2017,1,3)],
     [2, 3, datetime(2017,1,4)], 
     [2, 3, datetime(2017,1,5)], 
     [2, 4, datetime(2017,1,1)], 
     [3, 1, datetime(2017,1,2)]]

您可以设置df

df = pd.DataFrame(d)

并使用sort_values

sorted_df = df.sort_values(by=[0,1,2], ascending=[True,False,False])
sorted_list = sorted_df.agg(list, 1).tolist()


[[1, 2, Timestamp('2017-01-02 00:00:00')],
 [2, 4, Timestamp('2017-01-01 00:00:00')],
 [2, 3, Timestamp('2017-01-05 00:00:00')],
 [2, 3, Timestamp('2017-01-04 00:00:00')],
 [2, 3, Timestamp('2017-01-03 00:00:00')],
 [2, 2, Timestamp('2017-01-04 00:00:00')],
 [3, 1, Timestamp('2017-01-02 00:00:00')]]

请注意,第一列的升序排列,第二和第三列的降序排列,这当然是由于设置了ascending=[True,False,False]

答案 5 :(得分:0)

对于String,您可以使用一些公认的最大值(例如2 ^ 16或2 ^ 32)并使用chr(),unicode()或ord()来进行数学计算,就像整数一样。

在我的一项工作中,我知道我在utf8处理字符串并且它们的序数低于0xffff,所以我写道:

def string_inverse(s):
    inversed_string = ''
    max_char_val = 0xffff
    for c in s:
        inversed_string += unicode(max_char_val-ord(c))
    return inversed_string        

result.sort(key=lambda x:(x[1], string_inverse(x[0])), reverse=True)

x的类型为:(string,int),所以我得到的是滥用SQL:

select * from result order by x[1] desc, x[0] asc;

答案 6 :(得分:0)

试试这个:

>>> import functools
>>> reverse_key = functools.cmp_to_key(lambda a, b: (a < b) - (a > b))
>>> reverse_key(3) < reverse_key(4)
False
>>> reverse_key(3) > reverse_key(4)
True
>>> reverse_key('a') < reverse_key('b')
False