Pythonで多重ループ(ネストしたforループ)からbreak
Pythonで多重ループ(ネストしたforループ)からbreakする(抜け出す)方法について説明する。
Pythonにおけるforループの基本的な使い方については以下の記事を参照。
多重ループの書き方とbreakの注意点
Pythonにおける多重ループは以下のように書ける。Pythonではインデントでブロックを表すので、さらにインデントを加えるだけ。
l1 = [1, 2, 3]
l2 = [10, 20, 30]
for i in l1:
for j in l2:
print(i, j)
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# 2 30
# 3 10
# 3 20
# 3 30
多重ループの内側のループでbreak
した場合、内側のループから抜け出すのみで、外側のループ処理は続行される。
for i in l1:
for j in l2:
print(i, j)
if i == 2 and j == 20 :
print('BREAK')
break
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# BREAK
# 3 10
# 3 20
# 3 30
以下、内側のループの中からすべてのループを抜け出す方法について説明する。
方法1: elseとcontinueを利用
Pythonのfor文には、ループを抜けるbreak
の他にelse
とcontinue
がある。
これを使うと以下のように内側のループからすべてのループを抜け出すことが可能。
二重ループを例とする。
for i in l1:
for j in l2:
print(i, j)
if i == 2 and j == 20:
print('BREAK')
break
else:
continue
break
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# BREAK
説明を加えた処理とその結果を示す。
for i in l1:
print('Start outer loop')
for j in l2:
print('--', i, j)
if i == 2 and j == 20:
print('-- BREAK inner loop')
break
else:
print('-- Finish inner loop without BREAK')
continue
print('BREAK outer loop')
break
# Start outer loop
# -- 1 10
# -- 1 20
# -- 1 30
# -- Finish inner loop without BREAK
# Start outer loop
# -- 2 10
# -- 2 20
# -- BREAK inner loop
# BREAK outer loop
内側のループがbreak
ではなく正常に終了したときはelse
内のcontinue
が実行される。continue
は外側のループに対するもので、以降の処理(外側のループのbreak
)をスキップして次のサイクルに進む。
内側のループがbreak
で終了したときはelse
内のcontinue
が実行されず処理が続行される。この場合、外側のループのbreak
が実行される。
結果的に内側のループがbreak
で終了したときは必ず外側のループのbreak
も実行される。
三重ループは以下のようになる。ネストが深くなっても考え方は同じ。
l1 = [1, 2, 3]
l2 = [10, 20, 30]
l3 = [100, 200, 300]
for i in l1:
for j in l2:
for k in l3:
print(i, j, k)
if i == 2 and j == 20 and k == 200:
print('BREAK')
break
else:
continue
break
else:
continue
break
# 1 10 100
# 1 10 200
# 1 10 300
# 1 20 100
# 1 20 200
# 1 20 300
# 1 30 100
# 1 30 200
# 1 30 300
# 2 10 100
# 2 10 200
# 2 10 300
# 2 20 100
# 2 20 200
# BREAK
方法2: フラグ変数を追加
上述のelse
とcontinue
を使う方法はPythonの文法に詳しくないと理解が難しい。フラグ用の変数を使うと、おそらく多くの人にとってより理解しやすいコードになる。
内側のループがbreak
で終了する条件でフラグをTrue
とし、外側のループではそのフラグに応じてbreak
する。
二重ループの例。
l1 = [1, 2, 3]
l2 = [10, 20, 30]
flag = False
for i in l1:
for j in l2:
print(i, j)
if i == 2 and j == 20:
flag = True
print('BREAK')
break
if flag:
break
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# BREAK
三重ループは以下のようになる。ネストが深くなっても考え方は同じ。
l1 = [1, 2, 3]
l2 = [10, 20, 30]
l3 = [100, 200, 300]
flag = False
for i in l1:
for j in l2:
for k in l3:
print(i, j, k)
if i == 2 and j == 20 and k == 200:
flag = True
print('BREAK')
break
if flag:
break
if flag:
break
# 1 10 100
# 1 10 200
# 1 10 300
# 1 20 100
# 1 20 200
# 1 20 300
# 1 30 100
# 1 30 200
# 1 30 300
# 2 10 100
# 2 10 200
# 2 10 300
# 2 20 100
# 2 20 200
# BREAK
方法3: itertools.product()を使って多重ループを避ける
itertools.product()
を使って多重ループ自体を避ける方法もある。itertools.product()
についての詳細は以下の記事を参照。
itertools.product()
を使うと一つのループで複数のリストのすべての組み合わせを取得でき、多重ループと同様の結果が得られる。
import itertools
l1 = [1, 2, 3]
l2 = [10, 20, 30]
for i, j in itertools.product(l1, l2):
print(i, j)
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# 2 30
# 3 10
# 3 20
# 3 30
単独のループなので単に所望の条件でbreak
すればよい。
for i, j in itertools.product(l1, l2):
print(i, j)
if i == 2 and j == 20:
print('BREAK')
break
# 1 10
# 1 20
# 1 30
# 2 10
# 2 20
# BREAK
itertools.product()
の引数に追加していけば、三重ループおよびそれ以上の多重ループに相当する処理も可能。この場合も単独のループなので単にbreak
すればよい。
l1 = [1, 2, 3]
l2 = [10, 20, 30]
l3 = [100, 200, 300]
for i, j, k in itertools.product(l1, l2, l3):
print(i, j, k)
if i == 2 and j == 20 and k == 200:
print('BREAK')
break
# 1 10 100
# 1 10 200
# 1 10 300
# 1 20 100
# 1 20 200
# 1 20 300
# 1 30 100
# 1 30 200
# 1 30 300
# 2 10 100
# 2 10 200
# 2 10 300
# 2 20 100
# 2 20 200
# BREAK
注意点
itertools.product()
を使うと、要素に対して何らかの処理を行う場合、常にすべての組み合わせの回数実行される。
以下の例では、変数i
に対してもj
に対しても9回乗算が実行される。
for i, j in itertools.product(l1, l2):
x = i * 2 + j * 3
print(i, j, x)
# 1 10 32
# 1 20 62
# 1 30 92
# 2 10 34
# 2 20 64
# 2 30 94
# 3 10 36
# 3 20 66
# 3 30 96
多重forループの場合、外側のループに対する処理は外側の要素の数だけ実行される。
以下の例では、変数i
に対する乗算は3回のみ。
for i in l1:
temp = i * 2
for j in l2:
x = temp + j * 3
print(i, j, x)
# 1 10 32
# 1 20 62
# 1 30 92
# 2 10 34
# 2 20 64
# 2 30 94
# 3 10 36
# 3 20 66
# 3 30 96
重い処理を実行する場合や要素数が多い場合などはitertools.product()
を使うとロスが大きくなる可能性があるので注意。また、多重ループの場合でも処理の内容や要素数によってどちらを外側にするかで処理時間が大きく変わることもある。
各方式の速度比較
Jupyter Notebookのマジックコマンド%%timeit
で各方式の実行時間を計測した結果を示す。Pythonコードとして実行しても計測できないので注意。
なお、イテラブルオブジェクトの要素数、ネストするforループの数によって結果は異なるのであくまでも参考まで。
要素数100個の三重ループを例とする。
import itertools
n = 100
l1 = range(n)
l2 = range(n)
l3 = range(n)
x = n - 1
%%timeit
for i in l1:
for j in l2:
for k in l3:
if i == x and j == x and k == x:
break
else:
continue
break
else:
continue
break
# 43 ms ± 1.33 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
flag = False
for i in l1:
for j in l2:
for k in l3:
if i == x and j == x and k == x:
flag = True
break
if flag:
break
if flag:
break
# 45.2 ms ± 3.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
for i, j, k in itertools.product(l1, l2, l3):
if i == x and j == x and k == x:
break
# 55.8 ms ± 458 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
else
とcontinue
を使う方式とフラグ変数を追加する方式はおおよそ同等でitertools.product()
を使う方式が遅い。
フラグ変数を追加するとif
による条件分岐が増えるのでもっと遅くなるかと予想していたが、少なくともこの例ではelse
, continue
を使う方式とあまり差はなかった。
itertools.product()
はそもそも多重ループより遅いのがそのまま差となった模様。
なお、遅くてもコードの可読性が上がるというメリットはあるので、itertools.product()
のほうが適している場面もある。目的に応じて使い分ければよい。