2012-06-18 94 views
1

我有一个列表,其中包含像这样的项目的子列表。在Python 2.3中按项目频率对列表进行排序

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,并且需要找出最有效的方式来做到这一点。

+1

为什么选择Python 2.3? – Nima

+1

@尼玛有时候人们会在工作中陷入旧版本(并且缺乏/阻碍人们做出决定的升级)。我在这样的地方工作,所以OP可能没有选择。 – Levon

回答

2

通用的解决方案:使用list.sort具有关键函数返回一个元组:

mylist.sort(key=lambda sl: (sl.count('YES') + sl.count('MAYBE'), -sl.count('NO')), reverse=True) 

keyreverse在Python 2.4中添加,所以你必须手工进行:

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() 
+0

+1,但是OP可能需要用'key'来玩。主要思想是它应该返回一个元组。如果'YES''比''更好,我会建议像'(sl.count('YES'),sl.count('MAYBE'),-sl.count('NO'))' MAYBE''。 –

+1

我认为在python2.3中排序[作用有点不同](http://docs.python.org/release/2.3/lib/typesseq-mutable.html) – mata

+0

@mata谢谢,关于兼容性方法的附加信息 – ecatmur

3

要通过钥匙在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 

内置的版本已经是比较慢!由于增加了enumeratedecorate_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变换提供了一个解决方案,既快更广义的 - 一种罕见的奇妙结合。

+0

感谢您的详细分析。我会检查所有这些。非常感谢。 – GPX

+0

不幸的是,你的Schwartzian变换可以打破稳定的排序保证(如果'key(x)'和'key(y)'比较相等,但是'x'和'y'比较不等)。一个简单的解决方法是将索引存储在转换后的元组中:''seq = [(key(x),i,x)for i,x in enumerate(seq)]' – ecatmur

+0

此外,它不允许*一个'钥匙'和'cmp' - 如果你想要的话!在我的答案中,我已经用两个修复程序扩展了您的解决方案 – ecatmur

相关问题