36

在最近版本的 Python 中,将函数传递sort()给以前的cmp函数使我对某些对象执行复杂的排序变得更加棘手。

例如,我想使用一组字符串决胜局字段对一组对象从最新到最旧进行排序。所以我希望日期以相反的顺序排列,但字符串按自然顺序排列。使用比较功能,我可以将日期字段与字符串字段的比较反转。但是对于一个关键功能,我需要找到某种方法来反转/反转日期或字符串。

处理数字很容易(虽然丑陋) - 只需从某些东西中减去它们 - 但我是否必须为日期找到类似的技巧(从另一个日期减去它们并比较时间增量?)和字符串(......我不知道我如何以与语言环境无关的方式颠倒它们的顺序)。

我知道存在,functools.cmp_to_key()但它被描述为“主要用作转换为不再支持比较函数的 Python 3 的程序的转换工具”。这意味着我应该能够使用 key 方法做我想做的事——但是如何做呢?

4

7 回答 7

26

最通用的方法是简单地依次按每个键单独排序。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在你进行两次排序时调用)相比。

于 2012-06-26T12:26:52.967 回答
18

执行此操作的缓慢但优雅的方法是创建一个反向排序的值包装器:

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))
于 2012-06-26T12:42:36.143 回答
12

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

但是你的case作为一个函数显然更好cmp,所以用cmp_to_key它来实现它。

于 2012-06-26T12:41:09.140 回答
6

排序两次,每个键上一次,反转一次。

(Pythonsort稳定的;也就是说,它不会改变原始列表的顺序,除非它必须这样做。)

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

于 2012-06-26T12:23:10.000 回答
2

一种方法是使用pandaslibrary 和 args ascending,设置你想要升序排序的列和你想要降序的列,例如ascending=[True,False,False]

您不仅可以对两个级别(例如datetimestr)执行此操作,还可以对所需的任意数量的级别执行此操作。

例如,如果您有

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]

于 2018-07-09T20:05:31.287 回答
0

对于字符串,您可以使用一些公认的最大值(例如 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;
于 2016-04-14T10:02:48.380 回答
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
于 2021-01-06T03:04:57.773 回答