10

リストをインプレースで変更する必要がある Python スクリプト (v2.6) にいくつかのインスタンスがあります。ユーザーからのインタラクティブな入力に応じてリストから値をポップする必要があり、これを行う最もクリーンな方法を知りたいです。現在、私は a) 削除したいリスト内の項目を False に設定し、フィルターまたはリスト内包表記でそれらを削除する、または b) ループを通過中にまったく新しいリストを生成するという非常に汚い解決策を持っていますが、これは不必要に思われます名前空間に変数を追加し、メモリを消費します。

この問題の例は次のとおりです。

for i, folder in enumerate(to_run_folders):
    if get_size(folder) < byte_threshold:
        ans = raw_input(('The folder {0}/ is less than {1}MB.' + \
                    ' Would you like to exclude it from' + \
                    ' compression? ').format(folder, megabyte_threshold))
        if 'y' in ans.strip().lower():
            to_run_folders.pop(i)

リスト内の各フォルダーを確認したいと思います。現在のフォルダーが特定のサイズより小さい場合、それを除外するかどうかをユーザーに尋ねたいと思います。存在する場合は、リストからフォルダーをポップします。

このルーチンの問題は、リストを繰り返し処理すると、予期しない動作が発生して早期終了することです。スライスしてコピーを繰り返し処理すると、ポップは正しい値を取得しません。これは、インデックスがシフトされ、アイテムがポップされるにつれて問題が悪化するためです。スクリプトの他の領域でも、この種のリストを動的に調整する必要があります。この種の機能のためのクリーンな方法はありますか?

4

5 に答える 5

9

リストを逆方向にループするか、ビュー オブジェクトを使用できます。

リストを逆方向にループする方法については、https://stackoverflow.com/a/181062/711085を参照してください。基本的に使用しますreversed(yourList)(これにより、後方にアクセスするビューオブジェクトが作成されます)。

インデックス作成が必要な場合は、行うことができますが、実行する前に実行する必要があるreversed(enumerate(yourList))ため、メモリ内に一時的なリストが効果的に作成されます。インデックス操作を行うか、次のようにする必要があります。enumeratereversed

for i in xrange(len(yourList)-1, -1, -1):
    item = yourList[i]
    ...

さらにきれいな:reversedは を認識しているため、代わりにrange使用する場合は python3 または python2 でこれを行うことができます。xrange

for i in reversed(range(len(yourList))):  
    item = yourList[i]
    ...

(証明: できますがnext(reversed(range(10**10)))、python2 を使用している場合はコンピューターがクラッシュします)

于 2012-04-24T20:55:44.743 に答える
4

逆方向にループすることができます

後方:

x = range(10)
l = len(x)-1  # max index

for i, v in enumerate(reversed(x)):
    if v % 2:
        x.pop(l-i)  # l-1 is the forward index
于 2012-04-24T20:57:58.813 に答える
1

OK、溶液を測定しました。逆のソリューションはほぼ同じです。forwardwhileループは約 4 倍遅くなります。 しかし! Patrik のダーティソリューションは、100,000 個のランダムな整数のリストに対して約 80 倍高速です[Patrik2 のバグが修正されました] :

import timeit
import random

def solution_ninjagecko1(lst):
    for i in xrange(len(lst)-1, -1, -1):
        if lst[i] % 2 != 0:    # simulation of the choice
            del lst[i]
    return lst

def solution_jdi(lst):
    L = len(lst) - 1
    for i, v in enumerate(reversed(lst)):
        if v % 2 != 0:
            lst.pop(L-i)  # L-1 is the forward index
    return lst

def solution_Patrik(lst):
    for i, v in enumerate(lst):
        if v % 2 != 0:         # simulation of the choice
            lst[i] = None
    return [v for v in lst if v is not None]

def solution_Patrik2(lst):
    ##buggy lst = [v for v in lst if v % 2 != 0]
    ##buggy return [v for v in lst if v is not None]
    # ... corrected to
    return [v for v in lst if v % 2 != 0]

def solution_pepr(lst):
    i = 0                      # indexing the processed item
    n = 0                      # enumerating the original position
    while i < len(lst):
        if lst[i] % 2 != 0:    # simulation of the choice
            del lst[i]         # i unchanged if item deleted
        else:
            i += 1             # i moved to the next
        n += 1
    return lst

def solution_pepr_reversed(lst):
    i = len(lst) - 1           # indexing the processed item backwards
    while i > 0:
        if lst[i] % 2 != 0:    # simulation of the choice
            del lst[i]         # i unchanged if item deleted
        i -= 1                 # i moved to the previous
    return lst

def solution_steveha(lst):
    def should_keep(x):
        return x % 2 == 0
    return filter(should_keep, lst)

orig_lst = range(30)
print 'range() generated list of the length', len(orig_lst)
print orig_lst[:20] + ['...']   # to have some fun :)

lst = orig_lst[:]  # copy of the list
print solution_ninjagecko1(lst)

lst = orig_lst[:]  # copy of the list
print solution_jdi(lst)

lst = orig_lst[:]  # copy of the list
print solution_Patrik(lst)

lst = orig_lst[:]  # copy of the list
print solution_pepr(lst)

orig_lst = [random.randint(1, 1000000) for n in xrange(100000)]
print '\nrandom list of the length', len(orig_lst)
print orig_lst[:20] + ['...']   # to have some fun :)

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_ninjagecko1(lst)',
                  'from __main__ import solution_ninjagecko1, lst',
                  number=1)
print 'solution_ninjagecko1: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_jdi(lst)',
                  'from __main__ import solution_jdi, lst',
                  number=1)
print 'solution_jdi: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_Patrik(lst)',
                  'from __main__ import solution_Patrik, lst',
                  number=1)
print 'solution_Patrik: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_Patrik2(lst)',
                  'from __main__ import solution_Patrik2, lst',
                  number=1)
print 'solution_Patrik2: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_pepr_reversed(lst)',
                  'from __main__ import solution_pepr_reversed, lst',
                  number=1)
print 'solution_pepr_reversed: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_pepr(lst)',
                  'from __main__ import solution_pepr, lst',
                  number=1)
print 'solution_pepr: ', t

lst = orig_lst[:]  # copy of the list
t = timeit.timeit('solution_steveha(lst)',
                  'from __main__ import solution_steveha, lst',
                  number=1)
print 'solution_steveha: ', t

それは私のコンソールに印刷されます:

c:\tmp\_Python\Patrick\so10305762>python a.py
range() generated list of the length 30
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, '...']
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]

random list of the length 100000
[915411, 954538, 794388, 847204, 846603, 454132, 866165, 640004, 930488, 609138,
 333405, 986073, 318301, 728151, 996047, 117633, 455353, 581737, 55350, 485030,
'...']
solution_ninjagecko1:  2.41921752625
solution_jdi:  2.45477176569
solution_Patrik:  0.0468565138865
solution_Patrik2:  0.024270403082
solution_pepr_reversed:  2.43338888043
solution_pepr:  9.11879694207

それで、私はより長いリストで試しました。2倍の長さだけを使用すると、大きな違いが生じます(私の古いコンピューターでは)。Patrik のダーティソリューションは非常にうまく動作します。逆のソリューションよりも約 200 倍高速です。

random list of the length 200000
[384592, 170167, 598270, 832363, 123557, 81804, 319315, 445945, 178732, 726600,
516835, 392267, 552608, 40807, 349215, 208111, 880032, 520614, 384119, 350090, 
'...']
solution_ninjagecko1:  17.362140719
solution_jdi:  17.86837545
solution_Patrik:  0.0957998851809
solution_Patrik2:  0.0500024444448
solution_pepr_reversed:  17.5078452708
solution_pepr:  52.175648581

【ニンジャゲッコのコメントを受けて追記】

修正された Patrik2 解は、2 段階の Patrick 解よりも約 2 倍高速です。

要素の削除頻度がそれほど高くないことをシミュレートするために、テストのようなものif v % 2 != 0:が に変更されましたif v % 100 == 0:。その後、アイテムの約 1% を削除する必要があります。時間がかからないことは明らかです。リスト内の 500,000 個のランダムな整数の場合、結果は次のようになります。

random list of the length 500000
[403512, 138135, 552313, 427971, 42358, 500926, 686944, 304889, 916659, 112636,
791585, 461948, 82622, 522768, 485408, 774048, 447505, 830220, 791421, 580706, 
'...']
solution_ninjagecko1:  6.79284210703
solution_jdi:  6.84066913532
solution_Patrik:  0.241242951269
solution_Patrik2:  0.162481823807
solution_pepr_reversed:  6.92106007886
solution_pepr:  7.12900522273

Patrick のソリューションは、まだ約 30 倍高速です。

【2012/04/25追記】

その場で機能し、前方にループし、パトリックのソリューションと同じくらい高速な別のソリューション。要素が削除されたときにすべての末尾を移動するわけではありません。代わりに、必要な要素を最終的な位置に移動してから、リストの未使用の末尾を切り取ります。

def solution_pepr2(lst):
    i = 0
    for v in lst:
        lst[i] = v              # moving the element (sometimes unneccessary)
        if v % 100 != 0:        # simulation of the choice
            i += 1              # here will be the next one
    lst[i:] = []                # cutting the tail of the length of the skipped
    return lst

# The following one only adds the enumerate to simulate the situation when
# it is needed -- i.e. slightly slower but with the same complexity.        
def solution_pepr2enum(lst):
    i = 0
    for n, v in enumerate(lst):
        lst[i] = v              # moving the element (sometimes unneccessary)
        if v % 100 != 0:        # simulation of the choice
            i += 1              # here will be the next one
    lst[i:] = []                # cutting the tail of the length of the skipped
    return lst

上記のソリューションとの比較v % 100 != 0:

random list of the length 500000
[533094, 600755, 58260, 295962, 347612, 851487, 523927, 665648, 537403, 238660,
781030, 940052, 878919, 565870, 717745, 408465, 410781, 560173, 51010, 730322, 
'...']
solution_ninjagecko1:  1.38956896051
solution_jdi:  1.42314502685
solution_Patrik:  0.135545530079
solution_Patrik2:  0.0926935780151
solution_pepr_reversed:  1.43573239178
solution_steveha:  0.122824246805
solution_pepr2:  0.0938177241656
solution_pepr2enum:  0.11096263294
于 2012-04-24T22:53:26.227 に答える
1

現在、私は a) 削除したいリスト内のアイテムを False に設定し、フィルターまたはリスト内包表記でそれらを削除する、または b) ループを通過中にまったく新しいリストを生成するという非常に汚い解決策を持っていますが、これは不必要に思われます名前空間に変数を追加し、メモリを消費します。

実際、それはそれほど汚い解決策ではありません。リストの長さは通常どれくらいですか? リストには参照のみが含まれているため、新しいリストを作成してもそれほど多くのメモリを消費することはありません。

whileループをループして自分で列挙し、ユーザーが決定した場合に実行することもできdel lst[n]ます (元の位置を別々にカウントする可能性があります)。

于 2012-04-24T21:09:59.607 に答える
0

これを処理する最善の方法、つまり最も「Pythonic」な方法は、実際には、リストをループして、必要なフォルダーだけを含む新しいリストを作成することです。これが私がそれを行う方法です:

def want_folder(fname):
    if get_size(folder) >= byte_threshold:
        return True
    ans = raw_input(('The folder {0}/ is less than {1}MB.' + \
                ' Would you like to exclude it from' + \
                ' compression? ').format(folder, megabyte_threshold))
    return 'y' not in ans.strip().lower()

to_run_folders = [fname for fname in to_run_folders if want_folder(fname)]

リストが非常に大きい場合は、このソリューションのパフォーマンスについて心配し、汚いトリックを使用する必要があるかもしれません。しかし、リストが非常に大きい場合、表示される可能性のあるすべてのファイルについて人間が「はい/いいえ」の質問に答えるのはちょっとクレイジーかもしれません.

パフォーマンスは実際の問題ですか、それとも単なるしつこい心配ですか? 上記のコードは実際に使用するのに十分高速であり、トリッキーなコードよりも理解しやすく、変更しやすいと確信しているからです。

編集:@jdiは、コメントで、itertools.ifilter()またはを使用して提案しましたfilter()

私がテストしたところ、これは実際には上で示したものよりも高速であるはずです。

to_run_folders = filter(want_folder, to_run_folders)

@pepr のベンチマーク コードをコピーし、filter()ここに示すようにソリューションをテストしました。全体で 2 番目に速く、Patrik2 だけが速かった。Patrik2 は 2 倍高速でしたが、繰り返しになりますが、人間が「はい」または「いいえ」の質問に答えるのに十分なほど小さなデータ セットは、おそらく十分に小さいため、係数 2 はあまり重要ではありません。

編集: 楽しみのために、私は先に進み、純粋なリスト内包表記であるバージョンを書きました。評価する式は 1 つだけで、Python 関数呼び出しはありません。

to_run_folders = [fname for fname in to_run_folders
        if get_size(fname) >= mb_threshold or
                'y' not in raw_input(('The folder {0}/ is less than {1}MB.' +
                ' Would you like to exclude it from compression? '
                ).format(fname, mb_threshold)).strip().lower()

]

うん!私は関数を作る方がずっと好きです。

于 2012-04-24T23:37:38.110 に答える