与Python中的比较功能相比,“键”功能如何更有效?

时间:2018-10-20 23:57:14

标签: python python-3.x performance sorting

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)

在视频中,作者声称第二个选项比第一个选项更有效。

2 个答案:

答案 0 :(得分:2)

来自documentation

  

从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)

keysorted直接支持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]

函数的三行对应于成语名称的三个部分。

  1. 第一行通过创建一个元组来“修饰”数据,该元组将键值和一个tiebreaker索引与输入中的每个项目组合在一起。这是键函数被调用的唯一位置,因此只有O(len(data))个调用。

  2. 第二步很简单。我们使用默认的比较方法对装饰的元组列表进行排序。由于在字典上比较了元组,因此对于大多数项目对,仅需要将键值相互比较。如果键值之间有任何联系,则元组中间的索引值会破坏它(始终以使项保持与输入列表中的项相同的相对顺序的方式,使其成为稳定的排序方式) )。决不会比较data中的项目,因为决胜局值将始终不同(因为它们是原始列表的索引)。

  3. 最后的步骤也很容易理解。我们只是舍弃了key和tiebreaker值,从而产生了一个按新排序顺序仅包含原始项目的列表。

key参数添加到list.sortsorted时,它使sort函数能够自动为您完成全部任务。我不知道如何存储键值的确切细节(如果真的必须知道,可以使用read the source),但是它的作用与DSU习惯用法相同。在输入列表中的每个值上调用一次键函数,并在实际排序发生时保存其结果以供以后多次比较。