繰り返しながらリストからアイテムを削除するにはどうすればよいですか?


934

私はPythonのタプルのリストを繰り返し処理しており、特定の基準を満たしている場合は削除しようとしています。

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

代わりに何を使うべきcode_to_remove_tupですか?この方法でアイテムを削除する方法がわかりません。


このページのほとんどの回答は、リストの反復中に要素を削除すると奇妙な結果が生じる理由を実際には説明していませんが、この質問で受け入れられている回答はそうであり、おそらくこの問題に初めて遭遇する初心者にとってはましです。
ggorlen

回答:


827

リスト内包表記を使用して、削除したくない要素のみを含む新しいリストを作成できます。

somelist = [x for x in somelist if not determine(x)]

または、スライスsomelist[:]に割り当てることにより、既存のリストを変更して、必要なアイテムのみを含めることができます。

somelist[:] = [x for x in somelist if not determine(x)]

このアプローチはsomelist、変更を反映する必要がある他の参照がある場合に役立ちます。

理解の代わりに、を使用することもできますitertools。Python 2の場合:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

またはPython 3の場合:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

わかりやすくするため、そして[:]ハックやファジーという表記の使用を見つけた人のために、より明示的な代替案を示します。理論的には、空間と時間に関しては、上記のワンライナーと同じです。

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

また、Pythonリストのアイテム置換機能を持たない他の言語でも、最小限の変更で機能します。たとえば、すべての言語がFalsePythonのように空のリストをにキャストするわけではありません。のwhile somelist:ようなより明示的なものの代わりに使用できwhile len(somelist) > 0:ます。


4
いくつかが削除されることを知っている場合、つまり、それらを再書き込みするのではなく、それらのみを削除し、他のものはそのままにしておくと、より速くできますか?
highBandWidth

20
リストが膨大でコピーを作成する余裕がない場合はどうなりますか?
jpcgt 14年

15
@jpcgt somelist[:] = (x for x in somelist if determine(x))これを使用すると、不要なコピーを作成しない可能性があるジェネレータが作成されます。
Rostislav Kondratenko 2015

8
@RostislavKondratenko:内部的に呼び出しlist_ass_slice()を実装する関数。この関数は常にリストを返します。つまり、ジェネレーターの代わりにリストをすでに使用している@Alex Martelliのソリューションは、おそらくより効率的ですsomelist[:]=PySequence_Fast()
jfs

6
リスト内包表記をリストに割り当てることとリストクローンを割り当てることの違いは何ですか?元のリストsomelistは両方の方法で変更されませんか?
Bowen Liu

589

リストの内包を示唆する回答はほぼ正しいです。ただし、完全に新しいリストを作成し、古いリストと同じ名前を付けた場合を除き、古いリストは変更されません。@Lennartの提案のように、選択的削除によって行うこととは異なります-高速ですが、複数の参照を介してリストにアクセスする場合、参照の1つを再配置し、リストオブジェクトを変更しないという事実です。それ自体が、微妙で悲惨なバグにつながる可能性があります。

幸いにも、リスト内包の速度と必要なインプレース変更のセマンティクスの両方を取得するのは非常に簡単です。コードを書くだけです。

somelist[:] = [tup for tup in somelist if determine(tup)]

他の答えとの微妙な違いに注意してください:これはベアネームに割り当てられていません-たまたまリスト全体であるリストスライスに割り当てられているため、1つの参照を再配置するのではなく、同じPythonリストオブジェクト内のリストコンテンツ を置き換えます(前のリストオブジェクトから新しいリストオブジェクトへ)他の回答と同様です。


1
辞書で同じスライスされた割り当てを行うにはどうすればよいですか?Python 2.6では?
PaulMcG 2011年

11
@Paul:辞書は順序付けされていないため、スライスは辞書には意味がありません。dictの内容をdictの内容で置き換えたい場合ab、を使用してくださいa.clear(); a.update(b)
スヴェンマルナッハ、2011

1
変数が参照するものを置き換えることによって参照の1つを「再配置」してバグを引き起こすことができるのはなぜですか?それはシングルスレッドではなく、マルチスレッドのアプリケーションでのみ潜在的な問題となるようです。
Derek Dahmer、2011

59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];これxはリスト内包の結果に再割り当てしyますが、元のリストを引き続き参照します['foo','bar','baz']。同じリストを期待xyて参照している場合は、バグが発生している可能性があります。これを防ぐには、Alexが示すように、リスト全体のスライスに割り当てますx = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];。リストが適切に変更されます。リストへのすべての参照(ここxと両方y)が新しいリストを参照していることを確認します。
Steven T. Snyder

実際、filter関数を使用しても新しいリストが作成され、要素は変更されません...ただolist[:] = [i for i in olist if not dislike(i)]
John Strood

303

リストのコピーを取り、それを最初に反復する必要があります。そうしないと、反復が失敗し、予期しない結果になる可能性があります。

たとえば(リストの種類によって異なります):

for tup in somelist[:]:
    etc....

例:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

13
@Zen 2番目のリストはリストのコピーを反復処理するためです。したがって、元のリストを変更しても、反復するコピーは変更しません。
Lennart Regebro 2014年

3
list(somelist)と比較してsomelist [:]を実行することの利点は何ですか?
Mariusz Jamro、2015

3
list(somelist)イテラブルをリストに変換します。somelist[:]スライスをサポートするオブジェクトのコピーを作成します。したがって、彼らは必ずしも同じことをするわけではありません。この場合、somelistオブジェクトのコピーを作成したいので、以下を使用します[:]
Lennart Regebro

33
これを読んでいる人には注意してください、これはリストにとって非常に遅いです。remove()反復ごとにWHOLEリスト全体を調べる必要があるため、時間がかかります。
バイタル、2015

7
ダースアイテムのみのリストを扱う場合、ビッグOの時間は重要ではありません。多くの場合、将来のプログラマが理解できるように明確で単純な方が、パフォーマンスよりもはるかに価値があります。
Steve

127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

後方に移動する必要があります。そうしないと、座っている木の枝を切り取るような感じです:-)

Pythonの2人のユーザー:交換するrangeことでxrange、ハードコードのリストを作成しないように


13
Pythonの最近のバージョンでは、reversed()組み込み
ncoghlan

16
reversed()は新しいリストを作成せず、指定されたシーケンスに対して逆反復子を作成します。enumerate()と同様に、リストから実際にリストを取得するには、list()でラップする必要があります。あなたはの思考かもしれソート()、これはありません(それはそれを並べ替えることができますので、それが持っている)新しいリストを毎回作成します。
ncoghlan 2015

1
@Mauris enumerateはイテレータを返し、reversedシーケンスを期待するためです。reversed(list(enumerate(somelist)))メモリ内に追加のリストを作成してもかまわない場合は、そうすることができると思います。
drevicko

2
これは配列のO(N * M)です。大きなリストから多くのアイテムを削除すると、非常に遅くなります。だからお勧めしません。
Sam Watkins

2
@SamWatkinsええ、この答えは、非常に大きな配列からいくつかの要素を削除する場合のものです。メモリ使用量は少なくなりますが、m速度が遅くなる場合があります。
Navin、2016

52

公式のPython 2チュートリアル4.2。「ステートメント用」

https://docs.python.org/2/tutorial/controlflow.html#for-statements

ドキュメントのこの部分により、次のことが明確になります。

  • それを変更するには、反復リストのコピーを作成する必要があります
  • これを行う1つの方法は、スライス表記を使用することです。 [:]

ループ内で反復しているシーケンスを変更する必要がある場合(たとえば、選択したアイテムを複製するため)、最初にコピーを作成することをお勧めします。シーケンスを反復しても、暗黙的にコピーは作成されません。スライス表記はこれを特に便利にします:

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

Python 2ドキュメント7.3。「forステートメント」

https://docs.python.org/2/reference/compound_stmts.html#for

ドキュメントのこの部分は、コピーを作成する必要があることをもう一度言い、実際の削除例を示します:

注:シーケンスがループによって変更されているときは微妙です(これは、変更可能なシーケンス、つまりリストでのみ発生します)。次に使用される項目を追跡するために内部カウンターが使用され、これは反復ごとに増分されます。このカウンターがシーケンスの長さに達すると、ループが終了します。つまり、スイートがシーケンスから現在の(または前の)アイテムを削除すると、次のアイテムはスキップされます(既に処理された現在のアイテムのインデックスを取得するため)。同様に、スイートがシーケンス内の現在のアイテムの前にアイテムを挿入した場合、現在のアイテムはループを介して次回処理されます。これは、シーケンス全体のスライスを使用して一時的なコピーを作成することで回避できる厄介なバグにつながる可能性があります。たとえば、

for x in a[:]:
    if x < 0: a.remove(x)

ただし、値を見つけるためにリスト全体.remove()を反復処理する必要があるため、この実装には同意しません。

最善の回避策

どちらか:

  • 新しいアレイを最初から開始.append()し、最後に戻しますhttps : //stackoverflow.com/a/1207460/895245

    この時間は効率的ですが、反復中に配列のコピーを保持するため、スペース効率は低くなります。

  • delインデックスで使用:https : //stackoverflow.com/a/1207485/895245

    配列のコピーが不要になるため、スペース効率が向上します、CPythonリストは動的配列で実装されるため、時間効率が低下します

    つまり、アイテムを削除するには、次のすべてのアイテムを1つ戻す必要があります。つまり、O(N)です。

一般的に.append()は、メモリが大きな問題でない限り、デフォルトでより高速なオプションを選択するだけです。

Pythonはこれをもっとうまくできますか?

この特定のPython APIは改善できるようです。たとえば、以下と比較してください。

  • Java ListIterator :: remove「この呼び出しは、次または前の呼び出しごとに1回だけ実行できます」
  • std::vector::erase削除された要素に有効なインタレータを返すC ++

どちらを使用しても、イテレータ自体を除いて、反復されているリストを変更できないことは明らかであり、リストをコピーせずに効率的に変更できます。

おそらく、根本的な根拠は、Pythonリストは動的配列に基づいていると想定されているため、Javaはとの両方の実装ArrayListとのインターフェース階層が優れている一方で、いずれのタイプの削除も時間効率が悪いというLinkedListことですListIterator

Python stdlibにも明示的なリンクリストタイプがないようです:Python Linked List


48

そのような例に対するあなたの最善のアプローチは、リストの理解です

somelist = [tup for tup in somelist if determine(tup)]

determine関数を呼び出すよりも複雑なことをしている場合は、新しいリストを作成し、それに合わせて追加するだけです。例えば

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

を使用してリストをコピーするremoveと、以下の回答の1つで説明されているように、コードが少しきれいに見える場合があります。これは、最初にリスト全体をコピーし、O(n) remove削除される各要素に対して操作を実行してこれをO(n^2)アルゴリズムにするため、極端に大きなリストでは絶対に行わないでください。

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

37

関数型プログラミングが好きな人のために:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

または

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

1.リスト内包表記とジェネレータ式は、純粋な関数型言語であるHaskellから借用したものです。それらはとまったく同じように機能しfilter、よりPythonicです。2. またはlambdaを使用する必要がある場合は、リストcompまたはgenexprが常に優れたオプションです。また、transform / predicate関数がCで実装されたPython組み込みであり、反復可能オブジェクトがそれほど小さくない場合は少し速くなりますが、listcomp / genexprで回避できる必要がある場合は常に遅くなります。mapfiltermapfilterlambda
ShadowRanger 2016年

13

膨大なリストを使用してこれを行う必要があり、特に私の場合、残っているアイテムと比較して削除の数が少ないため、リストを複製することはコストがかかるように思われました。私はこの低レベルのアプローチを採用しました。

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

私が知らないのは、いくつかの削除を大きなリストをコピーするのに比べてどれほど効率的かということです。何か洞察があればコメントしてください。


私の場合、これらの「不要な」要素を別のリストに移動する必要があります。このソリューションについて何か新しいコメントはありますか?また、リストを複製するよりも、いくつかの削除を使用する方が良いと思います。
gustavovelascoh 2017

これは、パフォーマンスが問題になる場合の正しい答えです(@Alexeyと同じです)。とは言ってlistも、リストの中央からの削除にはリストの長さの直線的な時間がかかるので、そもそもデータ構造としての選択は慎重に検討する必要があります。k番目のシーケンシャルアイテムへのランダムアクセスが本当に必要ない場合は、おそらく検討してOrderedDictください。
最大

@GVelascohなぜ作成しないでnewlist = []、その後、およびnewlist.append(array[i])直前del array[i]
最大

2
これはおそらく時間効率が悪いことに注意してください。list()リンクリストの場合、ランダムアクセスはコストがかかります。list()配列の場合、後続のすべての要素を前に移動する必要があるため、削除はコストがかかります。まともなイテレータは、リンクリストの実装に適しています。ただし、これはスペース効率に優れています。
Ciro Santilli冠状病毒审查六四事件法轮功

10

現在のリストアイテムが目的の基準を満たしている場合は、新しいリストを作成することもできます。

そう:

for item in originalList:
   if (item != badValue):
        newList.append(item)

また、プロジェクト全体を新しいリスト名で再コーディングする必要がないようにします。

originalList[:] = newList

注意、Pythonのドキュメントから:

copy.copy(x)xの浅いコピーを返します。

copy.deepcopy(x)xのディープコピーを返します。


3
これにより、何年も前に承認された回答になかった新しい情報は追加されません。
Mark Amery 2016年

2
これはシンプルで、問題@MarkAmeryを調べるもう1つの方法です。圧縮されたコーディング構文を好まない人々にとっては、それほど凝縮されていません。
ntk4 2016年

9

この回答はもともと、重複としてマークされている質問への応答として書かれました: pythonのリストから座標を削除しています

コードには2つの問題があります。

1)remove()を使用すると、整数を削除しようとしますが、タプルを削除する必要があります。

2)forループはリストの項目をスキップします。

コードを実行するとどうなるか見てみましょう。

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

最初の問題は、 'a'と 'b'の両方をremove()に渡していますが、remove()は単一の引数しか受け入れないことです。それでは、remove()をリストで適切に機能させるにはどうすればよいですか?リストの各要素を理解する必要があります。この場合、それぞれがタプルです。これを確認するには、リストの1つの要素にアクセスしてみましょう(インデックス付けは0から始まります)。

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

ああ!L1の各要素は実際にはタプルです。これが、remove()に渡す必要があるものです。Pythonのタプルは非常に簡単で、括弧で値を囲むことで簡単に作成できます。「a、b」はタプルではありませんが、「(a、b)」はタプルです。したがって、コードを変更して再度実行します。

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

このコードはエラーなしで実行されますが、出力されるリストを見てみましょう。

L1 is now: [(1, 2), (5, 6), (1, -2)]

(1、-2)がまだリストにあるのはなぜですか?ループを使用してリストを反復しながらリストを変更することは、特別な注意なしに非常に悪い考えです。(1、-2)がリストに残る理由は、リスト内の各項目の位置がforループの反復間で変更されたためです。上記のコードに長いリストをフィードするとどうなるか見てみましょう。

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

その結果から推測できるように、条件ステートメントがtrueと評価されてリストアイテムが削除されるたびに、ループの次の反復では、リスト内の次のアイテムの評価がスキップされます。これは、その値が異なるインデックスに配置されているためです。

最も直感的なソリューションは、リストをコピーしてから、元のリストを反復処理し、コピーのみを変更することです。次のようにしてみてください:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

ただし、出力は以前と同じになります。

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

これは、L2を作成したときに、Pythonが実際に新しいオブジェクトを作成しなかったためです。代わりに、L2をL1と同じオブジェクトを参照するだけでした。これは、単に「等しい」(==)とは異なる「is」で確認できます。

>>> L2=L1
>>> L1 is L2
True

copy.copy()を使用して真のコピーを作成できます。その後、すべてが期待どおりに動作します。

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

最後に、L1の完全に新しいコピーを作成する必要がある場合よりも明確な解決策が1つあります。reverse()関数:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

残念ながら、reversed()がどのように機能するかを十分に説明することはできません。リストが渡されると、「listreverseiterator」オブジェクトを返します。実用上は、これを引数の逆のコピーを作成すると考えることができます。これが私がお勧めするソリューションです。


4

イテレーション中に他に何かしたい場合は、インデックス(たとえば、dictsのリストがある場合にそれを参照できることを保証します)と実際のリストアイテムの内容の両方を取得するとよいでしょう。

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerateアイテムとインデックスに一度にアクセスできます。reversed後で削除するインデックスが変更されないようにするためです。


辞書のリストがある場合、他の種類のリストの場合よりも、インデックスの関連性が高いのはなぜですか?私の知る限り、これは意味がありません。
Mark Amery 2016年


4

ここでの回答のほとんどは、リストのコピーを作成することを求めています。リストが非常に長い(1万1千個)ユースケースがあり、代わりにリストを減らし続ける方が賢明でした。

まず、foreachループをwhileループ置き換える必要があります

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

i古いアイテムが削除されると、新しいアイテムの値を同じインデックスから取得するため、ifブロックの値は変更されません。


3

forループを逆に試すことができるので、some_listの場合は次のようにします。

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

この方法では、インデックスが整列され、リストの更新の影響を受けません(cur要素をポップするかどうかに関係なく)。


ループオーバーreversed(list(enumerate(some_list)))は、自分でインデックスを計算するよりも簡単です。
Mark Amery 2016年

@MarkAmeryは、この方法でリストを変更できるとは考えていません。
Queequeg、2016年

3

考えられる解決策の1つは、いくつかのことを削除するだけでなく、すべての要素を1つのループで処理したい場合に役立ちます。

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

あなたは本当に理解力を使うべきです。彼らははるかに理解しやすいです。
Beefster

bad物事を削除し、それを使って何かを行いgood、1つのループで何かをしたい場合はどうすればよいですか?
Alexey

1
実は、開いたスライス(alist[:])を使ってリストのコピーを作成するという点で、多少の賢さがあることに気付きました。良いリビジョンは良いです。私の賛成票を取ってください。
ビーフスター

2

私は同様のことをする必要があり、私の場合、問題はメモリでした-リスト内の複数のデータセットオブジェクトをいくつかのことを行った後、新しいオブジェクトとしてマージする必要があり、マージする各エントリを取り除く必要がありましたそれらのすべてを複製してメモリを大量に消費することは避けてください。私の場合、リストの代わりに辞書にオブジェクトを置くとうまくいきました:

「」

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

「」


2

TLDR:

私はこれを可能にするライブラリを書きました:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

可能であれば、反復処理中に反復可能オブジェクトを変更する必要のない別の方法を使用するのが最善ですが、アルゴリズムによっては、それほど単純ではない場合があります。したがって、元の質問で説明されているコードパターンが本当に必要な場合は、それが可能です。

リストだけでなく、すべての変更可能なシーケンスで機能するはずです。


完全な答え:

編集:この回答の最後のコード例は、リスト内包表記を使用するのではなく、リストを所定の位置に変更したい場合があるユースケースを示しています。回答の最初の部分は、配列を適切に変更する方法のチュートリアルとして機能します。

解決策は、senderleからの(関連する質問に対する)この回答から続きます。これは、変更されたリストを反復処理しているときに配列インデックスがどのように更新されるかを説明しています。以下のソリューションは、リストが変更された場合でも配列のインデックスを正しく追跡するように設計されています。

ここfluidIter.pyからダウンロードしてください https://github.com/alanbacon/FluidIterator。これは単一のファイルなので、gitをインストールする必要はありません。インストーラーはないので、ファイルが自分のpythonパスにあることを確認する必要があります。コードはpython 3用に作成されており、python 2ではテストされていません。

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

これにより、次の出力が生成されます。

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

上記ではpop、流体リストオブジェクトに対してメソッドを使用しました。他の一般的な反復可能な方法はまた、として実装されていますdel fluidL[i].remove.insert.append.extend。リストはまた、スライスを使用して変更することができる(sort及びreverse方法が実装されていません)。

唯一の条件は、任意の時点で、fluidLまたはl別のリストオブジェクトに再度割り当てられた場合にコードが機能しなかった場合に、リストを変更する必要があることだけです。元のfluidLオブジェクトは引き続きforループで使用されますが、変更する範囲外になります。

すなわち

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

リストの現在のインデックス値にアクセスする場合は、列挙を使用できません。これは、forループが実行された回数を数えるだけだからです。代わりに、イテレータオブジェクトを直接使用します。

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

これは以下を出力します:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

このFluidIterableクラスは、元のリストオブジェクトのラッパーを提供するだけです。元のオブジェクトには、次のように流動オブジェクトのプロパティとしてアクセスできます。

originalList = fluidArr.fixedIterable

その他の例/テストはif __name__ is "__main__":、の下部のセクションにありfluidIter.pyます。これらは、さまざまな状況で何が起こるかを説明しているため、一見の価値があります。例:スライスを使用してリストの大きなセクションを置き換える。または、ネストされたforループで同じイテラブルを使用(および変更)します。

最初に述べたように、これはコードの可読性を損ない、デバッグをより困難にする複雑なソリューションです。したがって、David Raznickの回答で言及されているリスト内包表記など、他のソリューションを最初に検討する必要があります。そうは言っても、このクラスが私にとって便利で、削除が必要な要素のインデックスを追跡するよりも使いやすいクラスを見つけました。


編集:コメントで述べたように、この回答はこのアプローチが解決策を提供する問題を実際に提示するものではありません。私はここでそれに対処しようとします:

リスト内包表記は新しいリストを生成する方法を提供しますが、これらのアプローチは、リスト全体の現在の状態ではなく、各要素を個別に調べる傾向があります。

すなわち

newList = [i for i in oldList if testFunc(i)]

しかし、結果がすでにtestFunc追加されている要素に依存している場合はどうnewListでしょうか?または、まだその中にある要素がoldList次に追加される可能性がありますか?リスト内包表記を使用する方法はまだあるかもしれませんが、それは優雅さを失い始めるでしょう、そして私にとっては適切なリストを修正する方が簡単だと感じます。

以下のコードは、上記の問題に悩まされているアルゴリズムの一例です。アルゴリズムはリストを減らし、要素が他の要素の倍数にならないようにします。

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

出力と最終的な縮小リストを以下に示します

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

解決しようとしている問題が明確でないため、これが過度に設計されているかどうかを判断するのは困難です。このアプローチを使用して要素を削除すると、達成some_list[:] = [x for x in some_list if not some_condition(x)]できないことは何ですか?それに対する答えがなければ、タイプミスとコメントアウトされたコードを備えた600行のライブラリをダウンロードして使用する方が、ワンライナーよりも問題の解決策として優れていると誰もが信じるべきでしょうか?-1。
Mark Amery 2016年

@MarkAmery。アイテム自体だけでなく、リスト内の別のアイテムの状態またはリストの状態に基づいて、アイテムを削除(または追加または移動)する必要があるかどうかを判断しようとする場合の主な使用例全体。たとえば、リスト内包表記some_list[:] = [x for x in some_list if not some_condition(y)]yは、where がとは異なるリスト要素のようなものを書くことはできませんx。書くこともできませんsome_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)]
レゾナンス

2

最も効果的な方法はリストを理解することであり、多くの人が自分のケースを示します。もちろん、それiteratorを通過するための良い方法でもありfilterます。

Filter関数とシーケンスを受け取ります。Filter順番に各要素に渡された関数を適用し、その後、関数の戻り値であるか否かに応じて、要素を保持するか破棄するかどうかを決定しTrue、またはFalse

例があります(タプルのオッズを取得):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

注意:イテレータを処理することもできません。イテレータはシーケンスよりも優れている場合があります。


2

forループはインデックスを反復します。

あなたがリストを持っていると考えて、

[5, 7, 13, 29, 65, 91]

と呼ばれるリスト変数を使用していますlis。同じものを使用して削除します。

あなたの変数

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

5回目の反復の間、

あなたの35番は、あなたがそれをリストから削除プライムそうではなかったです。

lis.remove(y)

次に、次の値(65)が前のインデックスに移動します。

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

そのため、4番目の反復の完了ポインタは5番目に移動しました。

これが、前のインデックスに移動してからループが65をカバーしない理由です。

したがって、コピーではなくオリジナルを参照する別の変数にリストを参照しないでください。

ite = lis #dont do it will reference instead copy

ので、リストのコピーを行います list[::]

今あなたはそれを与えるでしょう、

[5, 7, 13, 29]

問題は、反復中にリストから値を削除すると、リストのインデックスが崩れることです。

代わりに理解力を試すことができます。

リスト、タプル、辞書、文字列など、すべての反復可能をサポートしています


これは、コードが失敗する理由を理解するのに役立ちました。
Wahid Sadik

2

反復中にリストから要素を削除する場合は、whileループを使用して、削除するたびに現在のインデックスと終了インデックスを変更できるようにします。

例:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1

1

他の回答は正解です。通常、反復しているリストから削除することは悪い考えです。逆反復は落とし穴を回避しますが、それを行うコードに従うのははるかに難しいため、通常はリスト内包表記またはを使用する方がよいでしょうfilter

ただし、反復しているシーケンスから要素を削除しても安全なケースが1つあります。それは、反復中に1つのアイテムのみを削除する場合です。これは、returnまたはを使用して確認できますbreak。例えば:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

これは、リストの最初のアイテムに何らかの条件を満たす副作用を伴う操作を実行し、その直後にそのアイテムをリストから削除する場合に、リスト内包よりも理解しやすいことがよくあります。


1

あなたの問題を解決するための3つのアプローチを考えることができます。例として、タプルのランダムなリストを作成しますsomelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]。私が選ぶ条件はsum of elements of a tuple = 15です。最後のリストには、合計が15に等しくないタプルのみが含まれます。

私が選んだのは、ランダムに選ばれた例です。タプルリストと選択した条件自由に変更しください

方法1.>提案したフレームワークを使用します(forループ内のコードを埋めます)。del上記の条件を満たすタプルを削除するのに小さなコードを使用します。ただし、このメソッドは、2つの連続して配置されたタプルが指定された条件を満たす場合、(上記の条件を満たす)タプルを見落とします。

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

方法2.>指定された条件が満たされない要素(タプル)を含む新しいリストを作成します(これは、指定された条件が満たされたリストの要素を削除するのと同じことです)。以下はそのためのコードです:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法3.>与えられた条件が満たされているインデックスを見つけ、それらのインデックスに対応する要素の削除(タプル)を使用します。以下はそのためのコードです。

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法1と方法2は方法3よりも高速です。Method2とmethod3はmethod1よりも効率的です。私はmethod2好みます。前述の例では、time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


0

本当に大きくなる可能性があるものについては、以下を使用します。

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

それは何よりもかなり速くなるはずです。


私が測定したものから、NumPyは、20を超える要素のリストの方が速くなり始め、1000要素以上の大きなリストのフィルタリングは12倍以上速くなります。
ジョージー

0

リストを一度に1項目ずつフィルタリングするだけではない状況では、反復中に反復を変更する必要があります。

これは、事前にリストをコピーすることが正しくなく、逆反復が不可能で、リストの理解もオプションではない例です。

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p

0

後で新しいリストを使用する場合は、elemをNoneに設定して、次のループで次のように判断するだけです。

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

これにより、リストをコピーする必要がなくなり、理解しやすくなります。


-1

数のリストを立ち上げ、3で割り切れるすべてのnoを削除したい

list_number =[i for i in range(100)]

を使用してlist comprehension、これは新しいリストを処理し、新しいメモリ空間を作成します

new_list =[i for i in list_number if i%3!=0]

lambda filter関数を使用して、結果の新しいリストを作成し、メモリ空間を消費します

new_list = list(filter(lambda x:x%3!=0, list_number))

新しいリストのメモリスペースを消費することなく、既存のリストを変更

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.