1

このようなアイテムのサブリストを含むリストがあります。

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'各行の s が多いほど、ソート リストの下位に移動します。

理想的な結果は —</p>

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に行き詰まっており、これを行う最も効率的な方法を見つける必要があります。

4

2 に答える 2

3

Python 2.3 以前でキーで並べ替えるには、パラメーターを使用できcmpます。ただし、keyスタイルの並べ替えの方が読みやすい場合もあります。いずれにせよ、関数は O(n) 回しか呼び出されないcmpのに対し、O(n log n) 回呼び出されるため、作業は少なくなります。key

それを念頭に置いて、key以降のバージョンの Python でパラメーターの動作を再現する方法を次に示します。これは、decorate-sort-undecorate イディオム、別名Schwartzian Transformを使用します。これはコピーを作成するため、スペース効率はそれほど高くありませんが、リストが大きい場合は、時間効率が向上する可能性があります。2.4 で追加されsortedた機能を大まかに再現しているため、この名前を付けました。sortedPython のバージョンを確認し、これを条件付きでインポートして、組み込みの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']]

また、辞書を使用して数えることを検討することもできます。そうすれば、必要なパスは 1 つだけです。ただし、少なくとも私のマシンではcount、3 つのパスが 1 つの 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):enumeratedecorate_sortdecorate_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

つまり、シュワルツ変換は、より高速より一般化されたソリューションを提供するということです。これは、まれで素晴らしい組み合わせです。

于 2012-06-18T14:38:13.000 に答える
2

一般的な解決策:list.sortタプルを返すキー関数で使用します。

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

keyPython 2.4で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、各アイテムの関数を 1 回だけ計算するソリューション (いわゆる「シュワルツ変換」) を使用することをお勧めします。>= 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()
于 2012-06-18T13:39:58.053 に答える