note.nkmk.me

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

Date: 2018-07-24 / tags: Python
このエントリーをはてなブックマークに追加

Pythonで多重ループ(ネストしたforループ)からbreakする(抜け出す)方法について説明する。

はじめに、

  • 多重ループの書き方とbreakの注意点

について説明したあと、多重ループからbreakする方法として、

  • else, continueを活用
  • フラグ変数を追加
  • itertools.product()を使って多重ループを避ける

の3つの方式について説明し、最後に、

  • 各方式の速度比較

を行う。

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

以下、内側のループの中からすべてのループを抜け出す方法について説明する。

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

フラグ変数を追加

上述の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

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すればOK。

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すればOK。

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

各方式の速度比較

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()のほうが適している場面は多い。目的に応じて使い分ければOK。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事