最新版本的Python中将 key 函数从前一个 cmp 函数传递给sort()
的举动让我更难以执行复杂的排序在某些物体上。
例如,我想要从最新到最旧的一组对象排序,并带有一组字符串tie-breaker字段。所以我希望日期顺序相反,但字符串按其自然顺序排列。使用比较函数,我可以反转日期字段与字符串字段的比较。但是通过关键功能,我需要找到一些方法来反转/反转日期或字符串。
与数字一样容易(虽然难看) - 只是从某些东西中减去它们 - 但我是否必须找到类似的日期(从另一个日期减去它们并比较timedeltas?)和字符串() ......我不知道我是如何以与语言环境无关的方式改变他们的顺序的。)
我知道functools.cmp_to_key()
的存在,但它被描述为"主要用作转换为Python 3的程序的转换工具,其中不再支持比较函数" 。这意味着我应该能够用关键方法做我想做的事 - 但是怎么做?
答案 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)
答案 4 :(得分:1)
一种方法是使用pandas
库和参数ascending
,通过执行以下操作来设置要排序的列和要降序的列: ascending=[True,False,False]
您不仅可以对 两个 级别(例如datetime
和str
)进行此操作,还可以对 任何< / 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