在Transforming Code into Beautiful, Idiomatic Python视频(链接从10:07开始)中,发言者说,使用key
代替cmp
比较功能进行排序更为有效。举例来说:他指出,由于存在O(nlog(n))个比较,使用1 key
时,对于1m个元素列表(log2(1m)〜20),比较函数将被调用20m次,因为它更好每个键只调用一次。
我理解他关于使用key
的可读性的观点,但是我不知道他如何确定每个键一次被调用。我本以为在内部,具有key
参数的排序函数看起来像这样:
def sort(sequence, key=None):
if key is None:
key = lambda x: x
def compare(first, second):
if key(first) < key(second): return -1
if key(second) < key(first): return 1
return 0
# ... implement sorting algorithm
因此,无论我们使用键函数还是比较函数,这将导致相同的时间复杂度。
如果有O(nlog(n))比较,键函数如何每个元素仅调用一次?这是Timsort实现细节吗?
编辑:
要发表评论,请举一个例子:
在python2中,sorted
函数接受了cmp
参数,如下所示:
def compare(first, second):
if len(first) < len(second): return -1
if len(second) < len(first): return 1
return 0
sorted(my_list, cmp=compare)
但是,您也可以根据需要传递键功能。因此,要获得与上述相同的效果,您可以这样做:
sorted(my_list, key=len)
在视频中,作者声称第二个选项比第一个选项更有效。
答案 0 :(得分:2)
从Python 2.4开始,list.sort()和sorted()都添加了一个关键参数,以指定要在进行比较之前的每个列表元素上调用的函数。
请注意在之前的单词:计算密钥不是代替调用cmp
,而是在之前。计算密钥需要O(n),但是排序需要O(n log(n)),因此排序的总体复杂度仍然是O(n log(n))。
已编辑
演示者建议在排序之前(通过keys
)计算字符串长度,这实际上需要对len
进行n次调用。如果在排序过程中(通过cmp
调用了相同的函数,则调用次数将至少为2 n log(n)。
简而言之,两种情况下的比较次数相同,但是对len
的调用次数不同。
答案 1 :(得分:2)
在key
和sorted
直接支持list.sort
之前,有一个习惯用法使您无论如何都能从中受益。这个习惯用语称为Decorate-Sort-Undecorate(显然也称为Schwartzian Transform)。
这是它的工作方式:
def sort_with_key(data, keyfunc):
decorated_data = [(keyfunc(x), i, x) for i, x in enumerate(data)]
decorated_data.sort()
return [x for key, i, x in decorated_data]
函数的三行对应于成语名称的三个部分。
第一行通过创建一个元组来“修饰”数据,该元组将键值和一个tiebreaker索引与输入中的每个项目组合在一起。这是键函数被调用的唯一位置,因此只有O(len(data))
个调用。
第二步很简单。我们使用默认的比较方法对装饰的元组列表进行排序。由于在字典上比较了元组,因此对于大多数项目对,仅需要将键值相互比较。如果键值之间有任何联系,则元组中间的索引值会破坏它(始终以使项保持与输入列表中的项相同的相对顺序的方式,使其成为稳定的排序方式) )。决不会比较data
中的项目,因为决胜局值将始终不同(因为它们是原始列表的索引)。
最后的步骤也很容易理解。我们只是舍弃了key和tiebreaker值,从而产生了一个按新排序顺序仅包含原始项目的列表。
将key
参数添加到list.sort
和sorted
时,它使sort函数能够自动为您完成全部任务。我不知道如何存储键值的确切细节(如果真的必须知道,可以使用read the source),但是它的作用与DSU习惯用法相同。在输入列表中的每个值上调用一次键函数,并在实际排序发生时保存其结果以供以后多次比较。