多重ループとリストの変更
二つの一覧から組を列挙し、breakがどのループを終えるか確認します。続いて連続する削除対象を含むリストを使い、見落としが起きる処理と対象を分けた処理を比較します。
説明
入れ子のループは外側の一回ごとに内側を反復します。一方、走査中に同じリストから削除すると、次に調べる位置と要素の位置がずれることがあります。
二つの一覧から組を列挙し、breakがどのループを終えるか確認します。続いて連続する削除対象を含むリストを使い、見落としが起きる処理と対象を分けた処理を比較します。
1二つの一覧を用意
拠点A/Bとポート80/443/8080の全組合せを作ります。
sites = ["A", "B"]; ports = [80, 443, 8080]二つの反復対象; pairs = []列挙結果の初期化; print(sites, ports)対象を表示外側を拠点にすると、Aの三ポートを先に列挙し、その後Bへ進みます。要素数は2×3の6組です。
2外側一回につき内側を一巡
各組を表示し、同じ順番でpairsへ追加します。
for site in sites:外側の拠点
for port in ports:拠点ごとに全ポート
pairs.append((site, port))組を記録
print(site, port)実行順Aの80・443・8080、続いてBの同じ三ポートです。内側だけを見ても、全体の列挙順は分からないため、外側のどの値にいるかも表示しています。
3breakが終えるのは内側だけ
443を表示したら内側を打ち切ります。外側には終了命令を書いていません。
for site in sites:継続する外側
for port in ports:打切り対象の内側
print(site, port)実行した組
if port == 443:
break内側だけを終了Aの80・443の後、Bの80・443も表示されます。breakは最も内側のループだけを抜けるため、拠点Bの処理は残ります。
4削除対象が連続するリスト
0以下を全て取り除くため、0と-1を連続して含む一覧を用意します。
numbers = [0, -1, 2, 0, 3]連続する削除対象; original = numbers.copy()元の内容を保持; print(numbers)開始時の並び期待する結果は[2, 3]です。まず同じリストを走査しながら削除する方法を試します。originalは後の比較用に残したコピーです。
5同じリストから削除すると飛ばされる
調べた値と、その時点のリストを表示します。
for value in numbers:変更対象をそのまま走査
print("visit", value, numbers)到達した値と現在の並び
if value <= 0:
numbers.remove(value)削除で後続の位置が変わる先頭0を削除すると-1が位置0へ移りますが、反復は次の位置1へ進むため-1を飛ばします。後半の0を削除した後も、移動した3が未確認になります。今回3は残す値なので結果だけではその見落としが分かりにくくなっています。
6残った値と検査漏れ
削除後の一覧と、まだ0以下が含まれているかを確認します。
print(numbers, any(value <= 0 for value in numbers))結果と残存条件を確認[-1, 2, 3]が残り、条件違反がTrueです。forが終了したことは、初期リストの全要素を一度ずつ検査できた証明にはなりません。
7走査対象と削除対象を分ける
元の一覧を走査し、削除は別のコピーへ行う関数を定義します。
def remove_nonpositive(data):
result = data.copy()変更用の別リスト
for value in data:元の一覧を安定して走査
if value <= 0:
result.remove(value)結果側だけを変更
return result新しい一覧を返すdataの順序と長さは反復中に変えず、変更はresultへ行います。removeは最初の一致要素を消す操作なので、大量のデータではこの繰返しの計算量にも注意が必要です。
8条件に合う値で新しい一覧を作る
コピーから削除する方法と、必要な要素だけを集める内包表記を比較します。
print(remove_nonpositive(original)変更対象を分けた削除); print([value for value in original if value > 0]残す要素を集める); print(original)入力が維持されたか確認どちらも[2, 3]で、originalは変わりません。必要な要素を新しい一覧へ集める方法なら、元の走査位置を変えず、繰り返しremoveを探す必要もありません。
まとめ
入れ子の順序とbreakの対象を、外側・内側の両方から確認します。
反復中に位置が変わる操作を避け、走査対象と変更対象を分けます。
操作履歴と解説を振り返れます。
Python 3
sites = ["A", "B"]; ports = [80, 443, 8080]; pairs = []; print(sites, ports)['A', 'B'] [80, 443, 8080]
for site in sites:
for port in ports:
pairs.append((site, port))
print(site, port)A 80 A 443 A 8080 B 80 B 443 B 8080
for site in sites:
for port in ports:
print(site, port)
if port == 443:
breakA 80 A 443 B 80 B 443
numbers = [0, -1, 2, 0, 3]; original = numbers.copy(); print(numbers)[0, -1, 2, 0, 3]
for value in numbers:
print("visit", value, numbers)
if value <= 0:
numbers.remove(value)visit 0 [0, -1, 2, 0, 3] visit 2 [-1, 2, 0, 3] visit 0 [-1, 2, 0, 3]
print(numbers, any(value <= 0 for value in numbers))[-1, 2, 3] True
def remove_nonpositive(data):
result = data.copy()
for value in data:
if value <= 0:
result.remove(value)
return resultprint(remove_nonpositive(original)); print([value for value in original if value > 0]); print(original)[2, 3] [2, 3] [0, -1, 2, 0, 3]
演習完了