今回は、
ループ処理でリストの要素削除処理を行う際の落とし穴
をPythonを使って紹介していきます。
・リストのデータ処理がうまく行かない
・リスト要素のデータ処理の最適解が分からない
そんな悩みを解決していきます。
僕がリストに初めて触れた頃は
配列めちゃめちゃ使えるぞ!
For文と組み合わせたら何でも出来ちゃうぞ!
配列最高!
なんて複数のデータをひとまとめに扱える感動を感じてました。
そんな便利な配列ですが、要素の削除に関しては注意が必要です。
僕は実際のサービス開発過程でデータの事前処理を任されバグを生んだ経験があります。
どこに落とし穴があるのか把握して、意識するだけで不具合の発生はかなり抑えられます。
今回は僕の痛い思い出を振り返りながら、今ならどう対応するかを紹介します。
リスト要素削除時の落とし穴
昔々のある日
値が0のデータを除外する事前処理プログラムを作ってくれ
配列をループ処理して条件分岐してあげれば余裕じゃん
とササッとこんなコードを書きました。
def remove_zero(num_list):
for num in num_list:
if num == 0:
num_list.remove(num)
return num_list
一見問題なさそうに見えますが、この関数にリストを渡して実行してみると結果はこうなります。
def remove_zero(num_list):
for num in num_list:
if num == 0:
num_list.remove(num)
return num_list
sample_list = [1, 0, 2, 0, 0]
remove_zero(sample_list)
print(sample_list)
実行結果
[1, 2, 0]
やってますねー。(汗)
当時はこんな落とし穴があるなんて知らずに自信満々にコード提出したのを覚えてます。
ちなみに上と同じ類の不具合が起きる書き方としてこんなのもあります。
def remove_zero_v2(num_list):
for x in range(len(num_list)):
if num_list[x] == 0:
del num_list[x]
return num_list
sample_list = [1, 0, 2, 0, 0]
remove_zero_v2(sample_list)
print(sample_list)
実行結果
IndexError: list index out of range
当時2つ目の例で書いていればエラーで気がついたんでしょうけどね・・・。
やってしまいました。
なぜ想定外の結果となるのか
それは、ループ処理でリスト処理する場合、処理に入った時点で要素に対するインデックス(番地)が決まるからです。
リストの要素を削除することでインデックス(番地)が変化してしまうので、
1つ目の例だと削除漏れが発生
2つ目の例だと「番地が想定数値を超えているよ」というエラー
になります。
どんな選択肢があるのか
では先程の失敗を回避する方法はどのように記述をすれば良いのか紹介します。
filter関数を使う
sample_list = [1, 0, 2, 0, 0]
sample_list = filter(lambda num: num != 0, sample_list)
print(*sample_list)
リスト内包表記を使う
sample_list = [1, 0, 2, 0, 0]
sample_list = [num for num in sample_list if num != 0]
print(sample_list)
参照渡しでオリジナルに対して操作をする
sample_list = [1, 0, 2, 0, 0]
for num in sample_list[:]:
if num == 0:
sample_list.remove(num)
print(sample_list)
while文を使う
while 0 in sample_list:
sample_list.remove(0)
print(sample_list)
後ろから処理していく
sample_list = [1, 0, 2, 0, 0]
for num in range(len(sample_list) - 1, -1, -1):
if sample_list[num] == 0:
del sample_list[num]
print(sample_list)
さいごに
色々紹介しましたが、「いろんな方法があるよ」ぐらいで頭に入れておくといいと思います。
コメント