Pythonで多重ループ(ネストしたforループ)からbreak

Modified: | Tags: Python

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の他にelsecontinueがある。

これを使うと以下のように内側のループからすべてのループを抜け出すことが可能。

二重ループを例とする。

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: フラグ変数を追加

上述のelsecontinueを使う方法は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)

elsecontinueを使う方式とフラグ変数を追加する方式はおおよそ同等でitertools.product()を使う方式が遅い。

フラグ変数を追加するとifによる条件分岐が増えるのでもっと遅くなるかと予想していたが、少なくともこの例ではelse, continueを使う方式とあまり差はなかった。

itertools.product()はそもそも多重ループより遅いのがそのまま差となった模様。

なお、遅くてもコードの可読性が上がるというメリットはあるので、itertools.product()のほうが適している場面もある。目的に応じて使い分ければよい。

関連カテゴリー

関連記事