私は2つのリストを持っています、
l1 = [1,2,3,4,5,6]
l2 = [3,2]
私が欲しいのは、l2にあるリストl1の要素を削除することです。そのために、私はこのようなことをしました。
for x in l1:
if x in l2:
l1.remove(x)
次のような出力が得られます
[1, 3, 4, 5, 6]
しかし、出力は次のようになります
[1, 4, 5, 6]
誰でもこれに光を当てることができます。
これはこのように簡単に説明できます。
あなたが持っている最初の配列を考えてみましょう:
| 1 | 2 | 3 | 4 | 5 | 6 |
今、あなたは反復を開始します
| 1 | 2 | 3 | 4 | 5 | 6 |
^
何も起こらず、イテレータは増分します
| 1 | 2 | 3 | 4 | 5 | 6 |
^
2が削除されます
| 1 | 3 | 4 | 5 | 6 |
^
イテレータの増分
| 1 | 3 | 4 | 5 | 6 |
^
そして出来上がり、3はまだそこにあります。
解決策は、ベクトルのコピーを反復処理することです。
for x in l1[:]: <- slice on entire array
if x in l2:
l1.remove(x)
または逆に繰り返す:
for x in reversed(l1):
if x in l2:
l1.remove(x)
これは次のように機能します。
| 1 | 2 | 3 | 4 | 5 | 6 |
^
| 1 | 2 | 3 | 4 | 5 | 6 |
^
| 1 | 2 | 4 | 5 | 6 |
^
| 1 | 2 | 4 | 5 | 6 |
^
| 1 | 4 | 5 | 6 |
^
| 1 | 4 | 5 | 6 |
^
少しシンプルにしてみませんか?l1
に存在する要素のみを削除したい場合は、実際に繰り返す必要はありませんl2
。
for item in l2:
while item in l1:
l1.remove(item)
これにより、希望する出力が正確に得られます...
また、コメント提供者が指摘しているように、重複する可能性がある場合は次のようになります。
l1 = filter(lambda x: x not in l2, l1)
..またはリスト内包表記を使用した他の多くのバリエーション。
外側のループに次のように読み取ります。
for x in l1[:]:
...
反復処理中にリストを変更して、妥当な結果を期待することはできません。上記のトリックは、コピーを作成l1
し、代わりにコピーを反復処理します。
出力リストで順序が問題にならず、要素が一意でハッシュ可能である場合は、セットを使用できることに注意してください。
set(l1).difference(l2)
出力としてセットが得られますが、そこから簡単にリストを作成できます。
l1 = list(set(l1).difference(l2))
l1 の重複の順序と損失が問題にならない場合:
list(set(l1) - set(l2))
最後の list() は、結果がリストとして必要な場合にのみ必要です。結果のセットをそのまま使用することもできます。これも反復可能です。順序付けが必要な場合は、もちろん、結果のリストで l.sort() を呼び出すことができます。
他の人が言ったように、リストをループしている間はリストを編集できません。ここでの適切なオプションは、リスト内包表記を使用して新しいリストを作成することです。
removals = set(l2)
l1 = [item for item in l1 if item not in removals]
セットのメンバーシップ チェックは、リストよりも大幅に高速であるため、セットを作成します。
編集:正しい結果が得られたにもかかわらず、直感的でない理由でそうし、あまり速くなかったため、元の回答を削除しました...だから私はタイミングを残しました:
import timeit
setup = """l1 = list(range(20)) + list(range(20))
l2 = [2, 3]"""
stmts = {
"mgilson": """for x in l1[:]:
if x in l2:
l1.remove(x)""",
"petr": """for item in l2:
while item in l1:
l1.remove(item)""",
"Lattyware": """removals = set(l2)
l1 = [item for item in l1 if item not in removals]""",
"millimoose": """for x in l2:
try:
while True: l1.remove(x)
except ValueError: pass""",
"Latty_mgilson": """removals = set(l2)
l1[:] = (item for item in l1 if item not in removals)""",
"mgilson_set": """l1 = list(set(l1).difference(l2))"""
}
for idea in stmts:
print("{0}: {1}".format(idea, timeit.timeit(setup=setup, stmt=stmts[idea])))
結果(Python 3.3.0 64ビット、Win7):
mgilson_set: 2.5841989922197333
mgilson: 3.7747968857414813
petr: 1.9669433777815701
Latty_mgilson: 7.262900152285258
millimoose: 3.1890831105541793
Lattyware: 4.573971325181478
l1
リストを繰り返し処理しているときにリストを変更すると、奇妙な動作が発生します。(3
反復中にスキップされます。)
コピーを反復処理するか、l2
代わりにアルゴリズムを変更して反復処理します。
for x in l2:
try:
while True: l1.remove(x)
except ValueError: pass
if x in l1
(これは、明示的にテストするよりもパフォーマンスが優れているはずl1
です。)いいえ、サイズが大きくなるにつれて、これはひどく実行されます。
FWIW @Tim Pietzckerがより現実的な入力データセットであると信じているものを使用し、さまざまな人々の回答のタイミングにもう少し厳密な(ただしそれ以外は同じ)アプローチを使用することにより、大幅に異なる結果が得られます。
名前とコード スニペットは Tim のものと同じですが、拒否するのではなく保持する要素を決定するという名前のバリエーションを追加したことを除いてLattyware
は Lattyware_rev
、前者よりも遅いことが判明しました。最速の 2 つは の順序を保持しないことに注意してくださいl1
。
最新のタイミングコードは次のとおりです。
import timeit
setup = """
import random
random.seed(42) # initialize to constant to get same test values
l1 = [random.randrange(100) for _ in xrange(100)]
l2 = [random.randrange(100) for _ in xrange(10)]
"""
stmts = {
"Minion91": """
for x in reversed(l1):
if x in l2:
l1.remove(x)
""",
"mgilson": """
for x in l1[:]: # correction
if x in l2:
l1.remove(x)
""",
"mgilson_set": """
l1 = list(set(l1).difference(l2))
""",
"Lattyware": """
removals = set(l2)
l1 = [item for item in l1 if item not in removals]
""",
"Lattyware_rev": """
keep = set(l1).difference(l2)
l1 = [item for item in l1 if item in keep]
""",
"Latty_mgilson": """
removals = set(l2)
l1[:] = (item for item in l1 if item not in removals)""",
"petr": """
for item in l2:
while item in l1:
l1.remove(item)
""",
"petr (handles dups)": """
l1 = filter(lambda x: x not in l2, l1)
""",
"millimoose": """
for x in l2:
try:
while True: l1.remove(x)
except ValueError: pass
""",
"K.-Michael Aye": """
l1 = list(set(l1) - set(l2))
""",
}
N = 10000
R = 3
timings = [(idea,
min(timeit.repeat(stmts[idea], setup=setup, repeat=R, number=N)),
) for idea in stmts]
longest = max(len(t[0]) for t in timings) # length of longest name
exec(setup) # get an l1 & l2 just for heading length measurements
print('fastest to slowest timings of ideas:\n' +\
' ({:,d} timeit calls, best of {:d} executions)\n'.format(N, R)+\
' len(l1): {:,d}, len(l2): {:,d})\n'.format(len(l1), len(l2)))
for i in sorted(timings, key=lambda x: x[1]): # sort by speed (fastest first)
print "{:>{width}}: {}".format(*i, width=longest)
出力:
fastest to slowest timings of ideas:
(10,000 timeit calls, best of 3 executions)
len(l1): 100, len(l2): 10)
mgilson_set: 0.143126456832
K.-Michael Aye: 0.213544010551
Lattyware: 0.23666971551
Lattyware_rev: 0.466918513924
Latty_mgilson: 0.547516608553
petr: 0.552547776807
mgilson: 0.614238139366
Minion91: 0.728920176815
millimoose: 0.883061820848
petr (handles dups): 0.984093136969
もちろん、根本的に異なる結果を説明する根本的な間違いがある場合はお知らせください。
l1 = [1, 2, 3, 4, 5, 6]
l2 = [3, 2]
[l1.remove(x) for x in l2]
print l1
[1, 4, 5, 6]