分岐とループの実践

分岐とループの実践

これまでに多くの理論を学びました——今こそ実際のプロジェクトを構築する時です。これは実践的なレッスンです。if 文、for ループ、while ループ、break、continue など、学んだ概念を使って、4 つの完全なプログラムを段階的に構築します。

各プロジェクトでは、要件を分析し、コードを書き、改善点について議論します。


プロジェクト 1:数当てゲーム(拡張版)

レッスン 08 で基本的な数当てゲームを書きました。今回は難易度選択、試行回数制限、「もう一度プレイ」機能を追加してアップグレードします。

要件

TEXT
1. プレイヤーが難易度を選択:Easy(1~50、無制限)、Normal(1~100、10 回)、Hard(1~200、7 回)
2. システムがランダムにターゲット数を生成
3. 推測ごとに「大きい」または「小さい」のヒントと残りの試行回数を表示
4. 正解するか試行回数を使い切ったら、「もう一度プレイ?」と尋ねる
5. q を入力して終了

完全なコード(⭐⭐⭐)

例:数当てゲーム

PYTHON
import random

print("=" * 30)
print("Number Guessing Game (Enhanced)")
print("=" * 30)

while True:  # 外側のループ:「もう一度プレイ」を制御
    # 難易度を選択
    print("\nSelect difficulty:")
    print("1. Easy (1~50, unlimited)")
    print("2. Normal (1~100, 10 tries)")
    print("3. Hard (1~200, 7 tries)")

    choice = input("Enter 1/2/3: ")

    if choice == "1":
        max_num = 50
        max_attempts = float("inf")  # 無制限
    elif choice == "2":
        max_num = 100
        max_attempts = 10
    elif choice == "3":
        max_num = 200
        max_attempts = 7
    else:
        print("Invalid choice, defaulting to Normal")
        max_num = 100
        max_attempts = 10

    # ターゲット数を生成
    target = random.randint(1, max_num)
    attempts = 0
    print(f"\nNumber generated between 1~{max_num}. Start guessing!")

    # 内側のループ:ゲームのメインロジック
    while True:
        # 試行回数を使い切ったか確認
        if attempts >= max_attempts:
            print(f"\nOut of attempts! The answer was {target}")
            break

        # プレイヤーの入力を取得
        guess_str = input(f"{max_attempts - attempts} tries left, enter your guess: ")

        if guess_str.lower() == "q":
            print("Quitting game.")
            max_attempts = -1  # 外側のループに通知するフラグ
            break

        # 入力を検証
        if not guess_str.isdigit():
            print("Please enter a valid number!")
            continue

        guess = int(guess_str)
        attempts += 1

        # 推測をチェック
        if guess < target:
            print("Too low, try higher!")
        elif guess > target:
            print("Too high, try lower!")
        else:
            print(f"\nCongratulations! The answer was {target}!")
            print(f"You got it in {attempts} tries.")
            break

    # 完全に終了すべきか確認
    if max_attempts == -1:  # プレイヤーが q を入力
        break

    # もう 1 回プレイするか尋ねる
    again = input("\nPlay again? (y/n): ")
    if again.lower() != "y":
        print("Thanks for playing, goodbye!")
        break
▶ 試してみよう

設計のポイント:外側のループは「もう一度プレイ」を制御し、内側のループは「推測プロセス」を制御します。max_attempts = -1 フラグは、プレイヤーが q を入力したときに両方のループから抜ける問題を解決します。よりエレガントな方法は、ゲームロジックを関数にカプセル化し、return で一度に終了することです。


プロジェクト 2:九九の表(複数のスタイル)

レッスン 08 では標準的な九九の表を出力しました。優れたプログラマーはループ構造を柔軟に制御して任意のスタイルを出力できるべきです。

スタイル 1:標準的な下三角(⭐⭐)

例:標準的な下三角

PYTHON
print("=== Standard Lower Triangle ===")
for i in range(1, 10):
    for j in range(1, i + 1):
        print(f"{j}x{i}={i*j}", end="\t")
    print()
▶ 試してみよう

スタイル 2:完全な長方形(⭐⭐)

例:完全な長方形

PYTHON
print("=== Full Rectangle ===")
for i in range(1, 10):
    for j in range(1, 10):
        print(f"{j}x{i}={i*j}", end="\t")
    print()
▶ 試してみよう

スタイル 3:上三角(⭐⭐⭐)

例:上三角

PYTHON
print("=== Upper Triangle ===")
for i in range(1, 10):
    # 位置揃えのための先頭スペースを出力
    for k in range(1, i):
        print(end="\t\t")
    # 九九の式を出力
    for j in range(i, 10):
        print(f"{i}x{j}={i*j}", end="\t")
    print()
▶ 試してみよう

出力:

TEXT
=== Upper Triangle ===
1x1=1    1x2=2    1x3=3    ...    1x9=9
          2x2=4    2x3=6    ...    2x9=18
                    3x3=9    ...    3x9=27
                              ...
                                        9x9=81

重要な洞察:上三角の課題は「スペースの配置」です——各行に (i-1) * 2 個のタブ文字のインデントが必要です。2 つの内側ループを使います:最初のループはスペースを出力し、2 番目のループは式を出力します。


プロジェクト 3:簡易電卓(拡張版)

レッスン 08 の練習問題で基本的な電卓を書きました。今回は履歴付きの連続演算をサポートする拡張版を作りましょう。

要件

TEXT
1. 現在の結果を 0 として開始
2. ユーザーが「演算子 数値」の形式で入力、例:「+ 5」
3. サポートする演算子:+ - * / ** // % clear exit
4. clear は結果を 0 にリセット
5. exit はプログラムを終了
6. 各操作の後に、現在の結果と履歴を表示

完全なコード(⭐⭐⭐)

例:拡張電卓

PYTHON
print("=" * 40)
print("Enhanced Calculator")
print("Format: operator number, e.g., + 5")
print("Supported: + - * / ** // %")
print("Special commands: clear reset exit quit")
print("=" * 40)

result = 0.0
history = []  # 操作履歴を保存

while True:
    # 現在の結果を表示
    print(f"\nCurrent result: {result}")

    # ユーザー入力を取得
    cmd = input(">>> ").strip()

    # 特殊コマンドを処理
    if cmd.lower() in ("exit", "quit"):
        print("Thanks for using!")
        break

    if cmd.lower() == "clear":
        result = 0.0
        history.clear()
        print("Reset to 0")
        continue

    # 通常の操作コマンドを解析
    parts = cmd.split()
    if len(parts) != 2:
        print("Invalid format! Enter: operator number")
        continue

    op, num_str = parts

    # 数値を検証
    if not num_str.replace(".", "").isdigit() or num_str.count(".") > 1:
        print("Invalid number!")
        continue

    num = float(num_str)

    # 操作を実行
    old_result = result
    if op == "+":
        result += num
    elif op == "-":
        result -= num
    elif op == "*":
        result *= num
    elif op == "/":
        if num == 0:
            print("Error: Cannot divide by zero!")
            continue
        result /= num
    elif op == "**":
        result **= num
    elif op == "//":
        if num == 0:
            print("Error: Cannot divide by zero!")
            continue
        result //= num
    elif op == "%":
        if num == 0:
            print("Error: Cannot divide by zero!")
            continue
        result %= num
    else:
        print(f"Invalid operator: {op}")
        continue

    # 履歴を記録
    history.append(f"{old_result} {op} {num} = {result}")

    # 結果を表示
    print(f"= {result}")

    # 最新の履歴 3 件を表示
    if len(history) >= 1:
        print("--- Recent History ---")
        for item in history[-3:]:
            print(f"  {item}")
▶ 試してみよう

プロジェクト 4:素数発見プログラム

ユーザーが範囲を入力し、その範囲内のすべての素数を出力してカウントします。

例:素数発見プログラム

PYTHON
print("Prime Number Finder")
print("Enter a range, and I'll find all primes within it.")

# 範囲を取得
start_str = input("Start value (>=2): ")
end_str = input("End value: ")

# 入力を検証
if not (start_str.isdigit() and end_str.isdigit()):
    print("Please enter valid positive integers!")
else:
    start = int(start_str)
    end = int(end_str)

    if start < 2:
        start = 2
        print("Start value adjusted to 2 (primes start from 2)")

    if start > end:
        print("Start value cannot be greater than end value!")
    else:
        primes = []  # 見つかった素数を保存

        # 範囲内の各数値をチェック
        for num in range(start, end + 1):
            # num が素数かどうかを確認
            is_prime = True
            for i in range(2, int(num ** 0.5) + 1):
                if num % i == 0:
                    is_prime = False
                    break

            if is_prime:
                primes.append(num)

        # 結果を出力
        print(f"\nFound {len(primes)} primes between {start} and {end}:")

        for i, prime in enumerate(primes, 1):
            print(f"{prime:5d}", end="")
            if i % 10 == 0:  # 10 個ごとに改行
                print()

        print()  # 最後の改行
▶ 試してみよう

最適化のヒント:数値が素数かどうかをチェックするとき、num-1 まで割る必要はなく、sqrt(num) までで十分です。numsqrt(num) より大きい因数がある場合、それとペアになる sqrt(num) より小さい因数が必ず存在します。この最適化は大きな範囲(100,000 以上)で非常に効果的です。


プロジェクト 5:BMI 健康指数+パスワード強度チェッカー

このプロジェクトでは、算術演算子、比較演算子、論理演算子、条件分岐、文字列メソッドを組み合わせて、2 つの実用的な問題を解決します。

パート 1:BMI 健康指数(⭐⭐)

BMI(Body Mass Index)は国際的に使用される体脂肪の測定基準です。計算式は簡単です:

BMI = 体重(kg) / 身長(m)^2

例:BMI 計算機

PYTHON
# BMI 健康指数計算機

# データ入力
weight = 68
height = 1.75

# BMI を計算
bmi = weight / (height ** 2)

print("=== BMI Health Index ===")
print(f"Weight: {weight} kg")
print(f"Height: {height} m")
print(f"BMI: {bmi:.1f}")

# 連鎖比較と if-elif でカテゴリを判定
if bmi < 18.5:
    print("Category: Underweight")
elif 18.5 <= bmi < 24:
    print("Category: Normal")
elif 24 <= bmi < 28:
    print("Category: Overweight")
else:
    print("Category: Obese")
▶ 試してみよう

パート 2:パスワード強度チェッカー(⭐⭐⭐)

ユーザーのパスワードが安全かどうかを判定するプログラムを書きます。パスワードの強度は、その長さと含まれる文字種によって決まります。

例:パスワード強度チェッカー

PYTHON
# パスワード強度チェッカー

password = "Py3#thon"

# パスワードの特性を分析
length = len(password)                    # 長さ
has_digit = any(c.isdigit() for c in password)     # 数字を含む
has_letter = any(c.isalpha() for c in password)    # 文字を含む
has_special = not password.isalnum()               # 特殊文字を含む

print("=== Password Strength Checker ===")
print(f"Password: {password}")
print(f"Length: {length}")
print(f"Contains digit: {has_digit}")
print(f"Contains letter: {has_letter}")
print(f"Contains special char: {has_special}")

# 論理演算子で条件を組み合わせて強度を判定
if length < 6:
    strength = "Weak"
elif length >= 10 and has_digit and has_letter and has_special:
    strength = "Strong"
elif length >= 8 and has_digit and has_letter:
    strength = "Medium"
else:
    strength = "Weak"

print(f"Password strength: {strength}")
▶ 試してみよう

よくある分岐/ループのミス

コードを書くとき、特にループ初心者は誰でもミスをします。最も典型的な落とし穴を紹介します。

ミス 1:無限ループ——条件変数の更新忘れ

PYTHON
# 間違った方法
n = 1
while n <= 10:
    print(n)
    # n += 1 を忘れた — 永遠に終わらない

# 正しい方法
n = 1
while n <= 10:
    print(n)
    n += 1

ミス 2:=== の混同

PYTHON
# 間違った方法
if score = 100:  # これは代入であり、比較ではない!
    print("Perfect!")

# 正しい方法
if score == 100:
    print("Perfect!")

ミス 3:反復中にリストを変更する

PYTHON
# 間違った方法 — 反復中に削除するとスキップが発生
numbers = [1, 2, 3, 4, 5]
for num in numbers:
    if num % 2 == 0:
        numbers.remove(num)
print(numbers)  # 期待通りにならない

# 正しい方法 — 新しいリストを作成
numbers = [1, 2, 3, 4, 5]
odd_numbers = [num for num in numbers if num % 2 != 0]
print(odd_numbers)

ミス 4:range() の終了値の誤解

PYTHON
# 間違い:range(5) が 1 2 3 4 5 を出力すると期待
for i in range(5):
    print(i)  # 実際の出力:0 1 2 3 4

# 正しい方法
for i in range(1, 6):
    print(i)  # 出力:1 2 3 4 5

よくあるユースケース

シナリオ ループ/分岐テクニック 説明
データ処理(フィルタリング、クリーニング) for + continue 無効なデータをスキップ
ユーザー対話メニュー while True + break 終了するまでメニューを繰り返し表示
ページネーション読み込み while + 条件 データがなくなるまで
検索/照合 for + break + else 見つかったら停止。見つからなければ else
バッチファイル処理 for + ネストロジック ディレクトリ内のすべてのファイルを反復
ゲームメインループ while True + イベント処理 継続的なレンダリングと入力応答

❓ よくある質問

Q 無限ループがよく発生します。何を確認すればよいですか?
A 3 つのことを確認してください:1)ループ条件はいつか False になりますか?2)ループ本体は条件変数を変更していますか?3)while True に対応する break がありますか?重要な変数の値を出力する print() を追加することが、無限ループのデバッグに最も効果的です。
Q range() とリストの反復、どちらが効率的ですか?
A range() は遅延シーケンスを返し、すべての数値を一度に生成しません。range(1000000) はほとんどメモリを使用しませんが、list(range(1000000)) は大量に使用します。大量の反復には、メモリ効率の点で range() を直接使用する方が優れています。
Q プロジェクトコードは以前の例よりずっと長いです。どう読めばよいですか?
A 3 つのステップ:まず、全体的な構造を理解します(ループ/条件の数)。次に、メインの流れを理解します(変数の変化)。最後に、エッジケースに注目します(入力検証、ゼロ除算など)。良いコードは自己文書化的です——重要なポイントに意味のある変数名とコメントがあります。

📖 まとめ


📝 練習問題

⭐:ナルシシスト数

「ナルシシスト数」は、各桁の 3 乗の和がその数自身と等しい 3 桁の数です。例:153 = 1^3 + 5^3 + 3^3

for ループを使ってすべてのナルシシスト数(4 つあります)を見つけてください。

ヒント:range(100, 1000) ですべての 3 桁の数を反復。//% で百の位、十の位、一の位を抽出。

⭐⭐:ひし形の出力

ユーザーから奇数 n を受け取り、* で作られたひし形をコンソールに出力します。

例(n=5):

TEXT
  *
 ***
*
 ***
  *

ヒント:ひし形には上半分と下半分があります。上半分は i を 1 から n//2 + 1 までループ、スペースを減らしアスタリスクを増やします。

⭐⭐⭐:学生スコア管理システム

以下の機能を実装するプログラムを書いてください:

TEXT
1. 学生のスコアを入力(q を入力で停止)
2. 合計、平均、最高、最低スコアを計算
3. 各範囲の学生数をカウント:90-100(優秀)、80-89(良)、70-79(普通)、60-69(合格)、<60(不合格)
4. スコアを高い順にソートして出力

要件:

100%