Python | データ構造:多重ループ

Python
スポンサーリンク

概要(多重ループは「ループの中でループ」を回す仕組み)

多重ループ(ネストしたループ)は、ある集合を外側で順に処理しつつ、各要素ごとにさらに内側の集合を繰り返す書き方です。定番の例は「行×列の走査」や「リストの中のリストを処理する」などで、2重ループが最もよく使われます。まずは動きのイメージを掴むことが大切です。

# 2重ループの基本形
for i in range(1, 4):          # 外側: 1,2,3
    for j in range(1, 4):      # 内側: 1,2,3(iごとに毎回回る)
        print(i, j)
# 出力は (1,1) (1,2) (1,3) (2,1) ... (3,3)
Python

基本構文と評価の流れ(ここが重要)

外側と内側の関係をイメージで理解する

外側ループの1回につき、内側ループが“全部”実行されます。つまり、外側が n 回・内側が m 回なら、合計で n×m 回の処理になります。これが「積の法則」で、2重ループが増えるほど処理回数は爆発的に増えます。

rows = ["A", "B"]
cols = [1, 2, 3]
for r in rows:          # 2回
    for c in cols:      # 3回(rごとに)
        print(r, c)     # 合計 2×3 = 6 回
Python

for と while の使い分け

決まった範囲やコレクションを回すなら for が基本です。ループ回数が条件で決まる(いつ終わるかは中で判定)なら while を使います。多重ループでも「外側は for、内側は while」のように混ぜて構いません。

# 外側は範囲、内側は条件
for i in range(3):
    j = 0
    while j < 2:
        print(i, j)
        j += 1
Python

よくある用途(表・座標・入れ子構造・組み合わせ)

九九表やグリッドの走査

行・列の2重ループは、最も直感的な例です。整形して見やすく出力する練習に向いています。

for a in range(1, 10):
    for b in range(1, 10):
        print(f"{a}×{b}={a*b}")
Python

二次元データ(リストのリスト)の処理

「1行ずつ取り出し、各行の要素を処理する」という形で入れ子を扱います。行の合計や特定条件の探索などが定番です。

matrix = [[1, 2, 3], [4, 5, 6]]
for row in matrix:
    total = 0
    for x in row:
        total += x
    print(total)  # 6, 15
Python

組み合わせ・直積(すべてのペアを列挙)

2つの集合の「すべての組み合わせ」を作るときに使います。目的がペア生成なら itertools.product が簡潔ですが、まずは基本の2重ループで理解しましょう。

letters = ["A", "B"]
nums = [1, 2, 3]
pairs = []
for L in letters:
    for n in nums:
        pairs.append((L, n))
print(pairs)  # [('A',1), ('A',2), ('A',3), ('B',1), ('B',2), ('B',3)]
Python

制御の深掘り(break・continue・else と早期終了)

最内ループを途中で止める(break)

break は「今いるループ」を中断します。多重ループでは“最も内側”だけが止まる点に注意。外側ごと止めたい場合はフラグや関数化で制御します。

for i in range(5):
    found = False
    for j in range(5):
        if i*j == 6:
            print("hit", i, j)
            found = True
            break             # 内側だけ終了
    if found:
        break                 # 外側も終了
Python

現在の反復をスキップ(continue)

continue は「その1回だけスキップして次へ」。条件付きで不要なケースを飛ばすと可読性が上がります。

for i in range(3):
    for j in range(3):
        if i == j:
            continue          # 同じ値のときはスキップ
        print(i, j)
Python

ループの else(途中で止まらなければ実行)

for/while の else は「break に遭遇しなかった場合」に実行されます。探索で“見つからなかった”後処理に便利です。

target = 7
for row in [[1,2,3], [4,5,6]]:
    for x in row:
        if x == target:
            print("found")
            break
    else:
        # 内側ループが break されなかったとき
        print("not in this row")
Python

パフォーマンスと設計(重要ポイントを深掘り)

計算量の爆発に注意する

2重で O(n×m)、3重なら O(n×m×k) です。データ規模が大きいと現実的な時間で終わりません。可能なら次の工夫を検討します。

  • 外側の要素をセット化して「存在判定」を O(1) にして内側の走査を省く
  • 事前にソートやインデックス構築で絞り込み
  • 条件を見直して早期終了(break)や continue で無駄を減らす
# 例:内側で「存在判定」するなら set にする
targets = set([3, 8, 10])   # 存在判定が速い
for row in [[1,2,3], [4,5,6], [7,8,9]]:
    for x in row:
        if x in targets:     # O(1) 目安
            print("hit", x)
Python

不要な3重以上は分解・再設計

3重ループが必要なケースでも、しばしば前処理で2重に落とせます。例:キー→値の辞書を作ってから参照する、片方をグループ化してから比較する、など。

# ある user_id と purchases を付き合わせて合計する
users = [{"id": 1}, {"id": 2}]
purchases = [{"user_id": 1, "price": 100}, {"user_id": 2, "price": 50}, {"user_id": 1, "price": 30}]

# 前処理で user_id→合計 を作る(1重)
by_user = {}
for p in purchases:
    by_user[p["user_id"]] = by_user.get(p["user_id"], 0) + p["price"]

# 参照は1重(合計2重で済む)
for u in users:
    print(u["id"], by_user.get(u["id"], 0))
Python

変数名の付け方とスコープの意識

i, j, k のような短名でも、何を表すかが分かるなら十分です。行なら r、列なら c、ユーザーなら uid など、意味が伝わる名前にしましょう。内側で外側の変数を上書きしないよう注意します。


実践的な例題(定番から一歩先まで)

例題1:行×列でテーブルを作る(文字整形)

rows, cols = 3, 4
for r in range(rows):
    line = []
    for c in range(cols):
        line.append(f"{r},{c}")
    print(" | ".join(line))
# 0,0 | 0,1 | 0,2 | 0,3
# 1,0 | 1,1 | 1,2 | 1,3
# 2,0 | 2,1 | 2,2 | 2,3
Python

例題2:二次元検索(見つけたら止める)

grid = [[2, 4, 6], [1, 3, 5], [7, 9, 11]]
target = 9
found_pos = None
for r, row in enumerate(grid):
    for c, val in enumerate(row):
        if val == target:
            found_pos = (r, c)
            break
    if found_pos:
        break
print(found_pos)  # (2,1)
Python

例題3:重複のないペアを作る(同じ要素同士は除外)

items = ["A", "B", "C"]
pairs = []
for i in range(len(items)):
    for j in range(i + 1, len(items)):  # i<j のみ作る
        pairs.append((items[i], items[j]))
print(pairs)  # [('A','B'), ('A','C'), ('B','C')]
Python

例題4:ネスト辞書の集計(条件付きで合計)

stores = [
    {"name": "A", "stock": {"coffee": 2, "tea": 0}},
    {"name": "B", "stock": {"coffee": 3, "tea": 4}},
]
total_tea = 0
for s in stores:
    for item, qty in s["stock"].items():
        if item == "tea" and qty > 0:
            total_tea += qty
print(total_tea)  # 4
Python

まとめ

多重ループは「外側の1回につき、内側が全部回る」仕組みで、グリッド処理・入れ子構造・組み合わせ列挙などに不可欠です。計算量は掛け算で増えるため、早期終了(break)、スキップ(continue)、セット化による高速な存在判定、前処理による段階的な削減で実行時間を抑えることが重要です。変数名は意味が伝わるように付け、3重以上で複雑になったら前処理や再設計で簡略化する判断を持つと、実務でも読みやすく堅牢なコードになります。

タイトルとURLをコピーしました