在Python 2.3中按项目频率对列表进行排序

时间:2012-06-18 13:33:23

标签: python list sorting

我有一个列表,包含这样的项目的子列表。

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 ,并且需要找到最有效的方法来实现这一目标。

2 个答案:

答案 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中添加了

keyreverse,因此您必须手动执行此操作:

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