要在 Python 2.3 或更低版本中按键排序,您可以使用cmp
参数。但有时key
样式排序更容易阅读;并且在任何情况下,它的工作量都会减少,因为cmp
将被调用 O(n log n) 次,而key
函数只会被调用 O(n) 次。
考虑到这一点,这是一种key
在更高版本的 Python 中重现参数行为的方法。它使用 decorate-sort-undecorate 成语,也就是Schwartzian Transform。这不会像复制副本那样节省空间,但是对于大型列表,它可能会更节省时间。我之所以命名它sorted
是因为它大致再现了sorted
2.4 中添加的功能;检查python版本并有条件地导入它,这样你就不会破坏sorted
新版本中的内置 - 或者只是重命名它。
def sorted(seq, key=lambda x: None, reverse=False):
seq = [(key(x), i, x) for i, x in enumerate(seq)]
seq.sort()
if reverse:
seq.reverse()
return [x for k, i, x in seq]
请注意,enumerate
仅当您关心对具有相等键的不相等值进行稳定排序时才需要这样做;它会减慢头发的功能。对您的数据进行了测试:
>>> key=lambda x: (x.count('YES'), x.count('MAYBE'), x.count('NO'))
>>> my_sorted(mylist, key=key, reverse=True)
[['ITEM C', 'YES', 'YES', 'YES', 'YES', 'NO', 'NO', 'MAYBE', 'NO', 'MAYBE'],
['ITEM B', 'YES', 'NO', 'YES', 'YES', 'NO', 'NO', 'NO', 'NO', 'MAYBE'],
['ITEM A', 'YES', 'NO', 'YES', 'YES', 'NO', 'NO', 'NO', 'NO', 'NO']]
您也可以考虑使用字典进行计数;这样,只需要一次通过。但是,经过充分优化,至少在我的机器上,count
三遍仍然比一个 Python循环快。for
因此,仅当您需要计算大量值时才使用它。我将把它留在这里以供后代使用:
def my_key(inner_list):
counts = {'YES':0, 'MAYBE':0, 'NO':0}
for i in inner_list:
if i in counts:
counts[i] += 1
return (counts['YES'], counts['MAYBE'], counts['NO'])
我做了一些测试;为长篇大论道歉。以下仅适用于好奇和好奇的人。
我的测试表明,在较小的列表中, decorate, sort, undecorate已经比使用内置的 sort +更快cmp
。在更大的列表中,差异变得更加显着。定义:
def key_count(x):
return (x.count('YES'), x.count('MAYBE'), x.count('NO'))
def key_dict(inner_list):
counts = {'YES':0, 'MAYBE':0, 'NO':0}
for i in inner_list:
if i in counts:
counts[i] += 1
return (counts['YES'], counts['MAYBE'], counts['NO'])
def decorate_sort(seq, key=lambda x: None, reverse=False):
seq = [(key(x), i, x) for i, x in enumerate(seq)]
seq.sort()
if reverse:
seq.reverse()
return [x for k, i, x in seq]
def builtin_sort(seq, key, reverse=False):
seq.sort(lambda p, q: cmp(key(p), key(q)))
if reverse:
seq.reverse()
测试:
>>> mylist = [
... ['ITEM A', 'YES', 'NO', 'YES', 'YES', 'NO', 'NO', 'NO', 'NO', 'NO'],
... ['ITEM B', 'YES', 'NO', 'YES', 'YES', 'NO', 'NO', 'NO', 'NO', 'MAYBE'],
... ['ITEM C', 'YES', 'YES', 'YES', 'YES', 'NO', 'NO', 'MAYBE', 'NO', 'MAYBE']
... ]
>>> %timeit decorate_sort(mylist, key=key_count, reverse=True)
100000 loops, best of 3: 5.03 us per loop
>>> %timeit builtin_sort(mylist, key=key_count, reverse=True)
100000 loops, best of 3: 5.28 us per loop
内置版本已经很慢了!由于添加了to ,不太通用的版本mylist.sort(lambda p, q: -cmp(key(p), key(q)))
对于一个简短的列表来说是更好的选择。没有它,速度会更快(在我之前的测试中每个循环 4.28 us):enumerate
decorate_sort
decorate_sort
>>> %timeit mylist.sort(lambda p, q: -cmp(key_count(p), key_count(q)))
100000 loops, best of 3: 4.74 us per loop
但是,在这种情况下使用key_dict
是一个错误:
>>> %timeit decorate_sort(mylist, key=key_dict, reverse=True)
100000 loops, best of 3: 8.97 us per loop
>>> %timeit builtin_sort(mylist, key=key_dict, reverse=True)
100000 loops, best of 3: 11.4 us per loop
在更大的列表上对其进行测试,结果基本相同:
>>> import random
>>> mylist = [[random.choice(('YES', 'MAYBE', 'NO')) for _ in range(1000)]
for _ in range(100)]
>>> %timeit decorate_sort(mylist, key=key_count, reverse=True)
100 loops, best of 3: 6.93 ms per loop
>>> %timeit builtin_sort(mylist, key=key_count, reverse=True)
10 loops, best of 3: 34.5 ms per loop
不太通用的版本现在比decorate_sort
.
>>> %timeit mylist.sort(lambda p, q: -cmp(key_count(p), key_count(q)))
100 loops, best of 3: 13.5 ms per loop
而且key_dict
还是比较慢。(但比builtin_sort
!)
>>> %timeit decorate_sort(mylist, key=key_dict, reverse=True)
10 loops, best of 3: 20.4 ms per loop
>>> %timeit builtin_sort(mylist, key=key_dict, reverse=True)
10 loops, best of 3: 103 ms per loop
所以结果是 Schwartzian 变换提供了一种更快、更通用的解决方案——一种罕见而美妙的组合。