要通过钥匙在Python 2.3排序或更低,你可以使用的cmp
参数。但有时key
风格排序更容易阅读;在任何情况下,它的工作量都会减少,因为cmp
将被称为O(n log n)次,而key
函数将只被调用O(n)次。
考虑到这一点,以下是在更高版本的Python中重现key
参数行为的方法。它使用装饰分类 - 未打磨的习惯用语,又名Schwartzian Transform。这不会像空间效率一样好,因为它可以制作副本,但对于大型列表,它可能更省时。我已命名为sorted
,因为它大致重现了2.4中添加的sorted
函数;检查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'])
我做了一些测试;为长期职位道歉。下面只是为了好奇和好奇。
我的测试表明,在较小的列表中,装饰,排序,undecorate是已经比使用内置排序+ 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
内置的版本已经是比较慢!由于增加了enumerate
到decorate_sort
,较少推广的版本mylist.sort(lambda p, q: -cmp(key(p), key(q)))
对于短名单是更好的;没有它,decorate_sort
更快(4.28我们每圈在我前面的测试):
>>> %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变换提供了一个解决方案,既快和更广义的 - 一种罕见的奇妙结合。
为什么选择Python 2.3? – Nima
@尼玛有时候人们会在工作中陷入旧版本(并且缺乏/阻碍人们做出决定的升级)。我在这样的地方工作,所以OP可能没有选择。 – Levon