我有一个列表,包含这样的项目的子列表。
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']
]
现在我想在这种情况下对子列表进行排序 - 每一行(即子列表)的项目'YES'
和'MAYBE'
越多,它就越高。每行'NO'
越多,它在排序列表中移动的越低。
理想的结果是 -
mylist = [
['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']
]
#Item C has 4 'YES' and 2 'MAYBE'
#Item B has 3 'YES' and 1 'MAYBE'
#Item C has 3 'YES'
可悲的是,我坚持使用 Python 2.3 ,并且需要找到最有效的方法来实现这一目标。
答案 0 :(得分:3)
要按Python 2.3或更低版本中的键进行排序,可以使用cmp
参数。但有时key
样式排序更容易阅读;并且在任何情况下,它的工作量较少,因为cmp
将被称为O(n log n)次,而key
函数将仅被调用O(n)次。
考虑到这一点,这里有一种方法可以在Python的更高版本中重现key
参数的行为。它使用了decorate-sort-undecorate习语,a.k.a。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
内置版本已经慢了!由于向mylist.sort(lambda p, q: -cmp(key(p), key(q)))
添加了enumerate
,因此较短通用的版本decorate_sort
更适合短名单。没有它,decorate_sort
更快(在我之前的测试中每循环4.28 us):
>>> %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变换提供了一个更快速和更广泛的解决方案 - 一种罕见且奇妙的组合。
答案 1 :(得分:2)
一般解决方案:使用list.sort
和一个返回元组的键函数:
mylist.sort(key=lambda sl: (sl.count('YES') + sl.count('MAYBE'), -sl.count('NO')), reverse=True)
在Python 2.4中添加了 key
和reverse
,因此您必须手动执行此操作:
key = lambda sl: (sl.count('YES') + sl.count('MAYBE'), -sl.count('NO'))
mylist.sort(lambda p, q: -cmp(key(p), key(q)))
如果key
速度很慢,最好使用仅在每个项目上计算key
函数的解决方案(所谓的“Schwartzian transform”)。请注意,> = Python 2.4已经执行此优化(或类似):
def key_sort(seq, cmp=None, key=None, reverse=False):
if key is not None:
transform = [(key(x), i, x) for i, x in enumerate(seq)]
transform.sort(None if cmp is None else lambda (k, _, _), (l, _, _): cmp(k, l))
seq[:] = [x for _, _, x in transform]
else:
seq.sort(cmp)
if reverse:
seq.reverse()