演習形式で学ぶPythonプログラミング vol.10 ~ for によるループ処理~

# まえがき
Python3.13環境を想定した演習問題を扱っています。変数からオブジェクト指向まできちんと扱います。続編として、データサイエンティストのためのPython(NumPyやpandasのような定番ライブラリ編)や統計学・統計解析・機械学習・深層学習・SQLをはじめCS全般の内容も扱う予定です。

対象者は、[1]を一通り読み終えた人、またそれに準ずる知識を持つ人とします。ただし初心者の方も、文法学習と同時並行で進めることで、文法やPythonそれ自体への理解が深まるような問題を選びました。前者については、答えを見るために考えてみることをお勧めしますが、後者については、自身と手元にある文法書を読みながら答えを考えるというスタイルも良いと思います。章分けについては[1]および[2]を参考にしました。

ストックしている問題のみすべて公開するのもありかなと考えたのですが、あくまで記事として読んでもらうことを主眼に置き、1記事1トピック上限5問に解答解説を付与するという方針で進めます。すべての問題を提供しない分、自分のストックの中から、特に読むに値するものを選んだつもりです。
また内容は、ただ文法をそのままコーディング問題にするのではなく、文法を俯瞰してなおかつ実務でも応用できるものを目指します。前者については世の中にあふれていますし、それらはすでに完成されたものです(例えばPython Vtuberサプーさんの動画[3]など)。これらを解くことによって得られるものも多いですし、そのような学習もとても有効ですが、私が選んだものからも得られるものは多いと考えます。
私自身がPython初心者ですので、誤りや改善点などがあればご指摘いただけると幸いです。

今回は「`for` によるループ処理」について扱います。それでは始めましょう！

# Q.10-1
##### 複数のリストを同時に `for` 文で処理する方法として、`zip()` を用いた構文とその応用例を記述せよ。

### 問題背景
#### 【1】 `zip()` 関数
複数のイテラブル(リスト、タプル、文字列など)を並行に処理するためにペアにする関数。長さが異なる場合、最短の長さに揃えて停止する。
```python
zip([1, 2, 3], ['a', 'b', 'c'])
→ (1, 'a'), (2, 'b'), (3, 'c')
```

例えば、`range(len(list))`との比較は以下。
| 方法                     | 問題点                                | `zip()` の利点                            |
|--------------------------|--------------------------------------|------------------------------------------|
| `range(len(list))` で回す | インデックス指定が必要で冗長           | 自動的にペアリングできて簡潔             |
| `zip()`                  | 並行処理が簡単                        | 読みやすく、エラーも起きにくい            |

### 解答例とコードによる実践
#### 【A：基本】2つのリストを `zip()` で同時処理を行う
```python
# 【1】2つのリストを用意
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

# 【2】zip() で対応する要素をペアにして繰り返し処理
for name, score in zip(names, scores):
    # 【3】名前と点数を表示する
    print(f"{name} さんの得点は {score} 点です。")
```
```python
# 実行結果
Alice さんの得点は 85 点です。
Bob さんの得点は 92 点です。
Charlie さんの得点は 78 点です。
```

#### 【B：応用】インデックス付きで複数リスト複数リストを処理( `enumerate` と組み合わせ)
```python
# 【1】3つのリストを用意
names = ['Alice', 'Bob', 'Charlie']
math_scores = [85, 92, 78]
english_scores = [88, 90, 80]

# 【2】enumerate(zip(...)) でインデックス付きで回す
for i, (name, math, english) in enumerate(zip(names, math_scores, english_scores), start=1):
    # 【3】名前と各科目の点数、合計点を表示する
    total = math + english
    print(f"{i}人目: {name} さん → 数学: {math} 点, 英語: {english} 点, 合計: {total} 点")
```
```python
# 実行結果
# 【1】3つのリストを用意
names = ['Alice', 'Bob', 'Charlie']
math_scores = [85, 92, 78]
english_scores = [88, 90, 80]

# 【2】enumerate(zip(...)) でインデックス付きで回す
for i, (name, math, english) in enumerate(zip(names, math_scores, english_scores), start=1):
    # 【3】名前と各科目の点数、合計点を表示する
    total = math + english
    print(f"{i}人目: {name} さん → 数学: {math} 点, 英語: {english} 点, 合計: {total} 点")
```
```python
1人目: Alice さん → 数学: 85 点, 英語: 88 点, 合計: 173 点
2人目: Bob さん → 数学: 92 点, 英語: 90 点, 合計: 182 点
3人目: Charlie さん → 数学: 78 点, 英語: 80 点, 合計: 158 点
```

#### 【注】長さが異なる場合の安全な処理
```python
from itertools import zip_longest

names = ['Alice', 'Bob']
scores = [85, 92, 78]

# 【zip_longest】は不足部分を埋めてくれる
for name, score in zip_longest(names, scores, fillvalue='N/A'):
    print(f"{name}: {score}")
```

## 【補足】
### 【1】 リスト内包表記との組み合わせ
通常の `for` 文だけでなく、リスト内包表記と `zip()` を組み合わせることで対応する要素同士を簡潔に加工した新しいリストを作ることが出来る。
```python
# 【1】2つのリスト
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

# 【2】リスト内包表記 + zip() を使って文字列のリストを作成
result = [f"{name}さんの得点は{score}点です" for name, score in zip(names, scores)]

# 【3】結果を表示
for line in result:
    print(line)
```
```python
Aliceさんの得点は85点です
Bobさんの得点は92点です
Charlieさんの得点は78点です
```

### 【2】 `zip()` のアンパック( `*` )応用
`zip()` は「まとめる」のみでなく、アンパック( `*` 演算子)を用いることで、まとめられたものを元の複数リストに戻す(逆変換)を行うことも出来る。これを「zipの展開(逆操作)」と呼ぶ。

```python
# 【1】リストのペア（タプルのリスト）
paired = [('Alice', 85), ('Bob', 92), ('Charlie', 78)]

# 【2】アンパックして2つのリストに戻す
names, scores = zip(*paired)

# 【3】結果を表示
print(f"names: {names}")
print(f"scores: {scores}")
```
```python
# 実行結果
names: ('Alice', 'Bob', 'Charlie')
scores: (85, 92, 78)
```
ここで、返されたのは「タプル」だが、必要なら `list(names)` などでリスト化可能。
cf.) 「複数リストをソートする（`zip` と `sort` の組み合わせ）」, 「`zip` を使った表データ（行列）操作」

# Q.10-2
#####  リストの内容がリストや辞書などのネスト構造である場合、それぞれの要素を再帰的に処理するコードを記述せよ。

### 問題背景
#### 【1】再帰(recursion)とは
関数が自分自身を呼び出す仕組みで、ネスト(入れ子)構造のデータを自然に処理できる。この際、必ず「終了条件」を設定しなければならない
```python
def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n-1)
```
#### 【2】`isinstance()` の使い方
`isinstance(obj, 型)` は、オブジェクトが特定の型かどうかを調べる関数。リストかどうか、辞書かどうかの判定に用いる。
```python
isinstance([1,2,3], list)  # True
isinstance({"a": 1}, dict) # True
```
複数型を同時にチェックすることも可能(例：`isinstance(x, (list, dict)`)

### 解答例とコードによる実践
#### 【A】再帰的にネストコードを処理するコード
リストの中にさらにリストがあったり、辞書が入れ子になっていると、通常のforループだけでは処理できない。そこで、リストや辞書があったら再帰的に関数を呼び出す設計が必要。

```python
# 【1】リストや辞書の中身を再帰的に処理する関数を定義する
def recursive_process(data):
    # 【2】もし data がリストの場合
    if isinstance(data, list):
        for element in data:
            # 【3】リストの各要素に対して再帰呼び出し
            recursive_process(element)
    # 【4】もし data が辞書の場合
    elif isinstance(data, dict):
        for key, value in data.items():
            # 【5】辞書のキーと値を表示
            print(f"キー: {key}")
            # 【6】値について再帰呼び出し
            recursive_process(value)
    # 【7】リストでも辞書でもなければ（通常の要素）
    else:
        # 【8】通常の値を表示
        print(f"値: {data}")

# 【9】テスト用のネストしたデータ構造を用意する
nested_data = [
    1,
    {"a": 2, "b": [3, 4, {"c": 5}]},
    [6, 7, {"d": [8, 9]}]
]

# 【10】再帰関数を呼び出して処理を実行する
recursive_process(nested_data)
```
```python
値: 1
キー: a
値: 2
キー: b
値: 3
値: 4
キー: c
値: 5
値: 6
値: 7
キー: d
値: 8
値: 9
```

#### 【B】`for` 文 + `isinstance()` 出方ごとに処理を分岐するコード
```python
# 【1】型が混在しているリストを作成
mixed_list = [10, "apple", [1, 2], {"key": "value"}, 3.14]

# 【2】for文で各要素を取り出して型ごとに分岐
for element in mixed_list:
    # 【3】リストだったら
    if isinstance(element, list):
        print(f"リスト型の要素: {element}")
    # 【4】辞書だったら
    elif isinstance(element, dict):
        print(f"辞書型の要素: {element}")
    # 【5】整数型だったら
    elif isinstance(element, int):
        print(f"整数型の要素: {element}")
    # 【6】文字列型だったら
    elif isinstance(element, str):
        print(f"文字列型の要素: {element}")
    # 【7】その他（ここでは浮動小数点数など）
    else:
        print(f"その他の型の要素: {element}")
```
```python
整数型の要素: 10
文字列型の要素: apple
リスト型の要素: [1, 2]
辞書型の要素: {'key': 'value'}
その他の型の要素: 3.14
```

| 工夫点                  | 内容                                     |
|-------------------------|----------------------------------------|
| isinstance() で型判定    | リストと辞書で分岐処理を行う               |
| 再帰呼び出しを活用       | ネストの深さに関係なくすべての要素を処理できる |
| ベースケースをしっかり設定 | リストでも辞書でもない場合はそのまま出力する     |

ネスト構造を再帰的に処理することで、どんな深さのデータでも安全に処理できる。再帰には「型ごとの分岐」と「終了条件」が不可欠。Pythonicに設計するためには、`isinstance()` で型チェックを明確にすることが重要。

# Q.10-3
##### `for` 文で辞書の `.items()` を処理しつつ、値に特定の操作を行うコードを記述せよ。この際、辞書の条件付きフィルタリングについても併せて考えること。

### 問題背景
#### 【1】辞書の `.items()` メソッド
`.items()` メソッドを用いると、辞書の `key`, `value` のペアを取り出すことが出来る。`for key, value in dict.items():` の形で、1回のループで `key`, `value` を同時に扱える。

```python
my_dict = {"a": 1, "b": 2}
for key, value in my_dict.items():
    print(key, value)
```

| 方法                         | 問題点                                   | `.items()` の利点                      |
|------------------------------|----------------------------------------|----------------------------------------|
| `for key in dict:`            | キーしか取れないので都度 `dict[key]` が必要 | キーも値も同時に取り出せる              |
| `for key, value in dict.items()` | 直感的に処理でき、可読性が高い               | コードがシンプル・エラーが少ない         |


#### 【2】 辞書内包表記
リスト内包表記同様に、1行で新しい辞書を作る方法。これにより、辞書の各ペアを一括で変換でき、簡潔に作成することが出来る。
```python
{キー: 値 for キー, 値 in 既存の辞書.items()}
```

#### 【3】辞書の条件付きモニタリング
辞書内包表記の中に `if` 文を書き込むことが出来る。条件に合うペアのみを取り出す。
```python
{キー: 値 for キー, 値 in 辞書.items() if 条件}
```

### 解答例とコードによる実践
**辞書の値(数値)をすべて2倍にする** ことを考える
```python
# 【1】対象の辞書を作成する
scores = {"Alice": 50, "Bob": 70, "Charlie": 90}

# 【2】for文で辞書の items() を使ってキーと値を取り出す
for name, score in scores.items():
    # 【3】各値（score）を2倍する
    new_score = score * 2
    # 【4】結果を表示する
    print(f"{name} さんの新しいスコアは {new_score} 点です。")
```
```python
# 実行結果
Alice さんの新しいスコアは 100 点です。
Bob さんの新しいスコアは 140 点です。
Charlie さんの新しいスコアは 180 点です。
```

もとの辞書自体を更新したい場合は、以下のように書くとよい。
```python
# 【1】対象の辞書を作成する
scores = {"Alice": 50, "Bob": 70, "Charlie": 90}

# 【2】for文でキーを回して、値を更新する
for name in scores:
    # 【3】スコアを2倍にして辞書を更新する
    scores[name] *= 2

# 【4】更新後の辞書を表示する
print(scores)
```
```python
# 実行結果
{'Alice': 100, 'Bob': 140, 'Charlie': 180}
```

辞書内包表記で表記すると以下
```python
# 【1】元の辞書
scores = {"Alice": 50, "Bob": 70, "Charlie": 90}

# 【2】辞書内包表記で値を2倍にした新しい辞書を作成
doubled_scores = {name: score * 2 for name, score in scores.items()}

# 【3】結果を表示
print(doubled_scores)
```

また、スコアが80点以上の人だけを抽出したい場合は以下
```python
# 【1】元の辞書
scores = {"Alice": 50, "Bob": 70, "Charlie": 90, "David": 85}

# 【2】80点以上の人だけ抽出
high_scores = {name: score for name, score in scores.items() if score >= 80}

# 【3】結果を表示
print(high_scores)
```
```python
# 実行結果
{'Charlie': 90, 'David': 85}
```

上記をまとめると以下
| 工夫点                             | 内容                                                   |
|------------------------------------|--------------------------------------------------------|
| `.items()` を使う                  | キーと値を同時に扱い、シンプルに記述できる              |
| 直接更新するか、新たに変数を作るか選べる | データを壊したくないなら新辞書、新規作成が安全           |
| 値に対する処理を柔軟に設計できる         | 2倍だけでなく文字列操作などにも応用できる               |


# Q.10-4
**リストの各要素に対して `try–except` を組み込み、処理失敗時にエラーログを記録するループを記述せよ。** 

### 問題背景
#### 【1】`try-except` 文
Pythonでは実行時エラー(例外)が発生する可能性がある。この際、`try` 節で `except` 節で補足してプログラムのクラッシュを防ぐ。
```python
try:
    risky_code()
except Exception as e:
    handle_error(e)
```

#### 【2】複数の `except` を用いたエラー分類
Pythonでは、`except` 節を複数用意することで、異なる例外型ごとに適切な処理を行える。また、`else` 節(：例外が出なかったときに実行)や、`finally` 節(：例外の有無にかかわらず必ず最後に実行)との組み合わせも可能である。

```python
try:
    処理
except ZeroDivisionError:
    ゼロ除算時の処理
except TypeError:
    型エラー時の処理
else:
    # 例外が発生しなかった場合に実行する処理
    print("例外は発生しませんでした。")
finally:
    # 例外の有無にかかわらず、必ず最後に実行する処理
    print("try-except-finally ブロックの実行が完了しました。")
```

#### 【3】`logging` モジュール
ログ出力の際に用いる `logging` モジュールは、ログレベル（INFO, WARNING, ERRORなど）の設定によって、`print()` よりも柔軟にログ記録が可能。
```python
import logging
logging.basicConfig(level=logging.ERROR)
logging.error("エラーが発生しました")
```

### 解答例とコードによる実践
```python
# 【1】logging モジュールを読み込む
import logging

# 【2】ログの設定：ERROR 以上のレベルを出力する
logging.basicConfig(level=logging.ERROR, format="%(levelname)s:%(message)s")

# 【3】処理対象のリスト（エラーを含む例）
data_list = [10, 5, 0, 'a', 2]

# 【4】各要素に対して処理（ここでは10を割る）を試みる
for i, item in enumerate(data_list):  # 【5】ループ処理の開始（インデックス付き）
    try:
        # 【6】10を item で割る処理を実行（エラーの可能性あり）
        result = 10 / item
        print(f"[{i}] 成功: 10 / {item} = {result}")
    except Exception as e:
        # 【7】エラーが発生した場合、ログに記録（エラーレベル）
        logging.error(f"[{i}] 処理失敗: 10 / {item} → {e}")
```
```
# 実行結果
[0] 成功: 10 / 10 = 1.0
[1] 成功: 10 / 5 = 2.0
ERROR:[2] 処理失敗: 10 / 0 → division by zero
ERROR:[3] 処理失敗: 10 / a → unsupported operand type(s) for /: 'int' and 'str'
[4] 成功: 10 / 2 = 5.0
```

更に応用例を示す。
```python
# 【1】logging をインポートして設定する
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")

# 【2】処理対象のデータ
data_list = [5, 0, 'a', 2, None]

# 【3】結果を格納するリスト
results = []

# 【4】ループ処理で例外ごとに処理を分岐
for i, item in enumerate(data_list):
    try:
        # 【5】10をitemで割る
        result = 10 / item
    except ZeroDivisionError:
        # 【6】0で割ったときの処理：0を代入
        logging.warning(f"[{i}] ゼロ除算のため、0 を代入しました")
        result = 0
    except TypeError:
        # 【7】型が不正なときの処理：Noneを代入
        logging.warning(f"[{i}] 型エラーのため、None を代入しました")
        result = None
    except Exception as e:
        # 【8】それ以外のエラー：ログ出力＋None代入
        logging.error(f"[{i}] その他のエラー ({type(e).__name__}): {e}")
        result = None
    else:
        # 【9】成功時の処理
        logging.info(f"[{i}] 成功: 10 / {item} = {result}")
    finally:
        # 【10】結果リストに追加
        results.append(result)

# 【11】最終的な結果を表示
print("結果:", results)
```
```
# 実行結果
WARNING:[1] ゼロ除算のため、0 を代入しました
WARNING:[2] 型エラーのため、None を代入しました
WARNING:[4] 型エラーのため、None を代入しました
結果: [2.0, 0, None, 5.0, None]
```

## 【補足】
### 【1】`dict.get()` や `try/except` を組み合わせた辞書処理の安全性
辞書から `key` を取得する際、存在しない `key` に対して、 `dict[key]` を用いると `KeyError` が発生する。これを回避するための手段として以下がある。
1) `dict.get(key)`： `key` がなければ `None` を返す
2）`dict.get(key, default)`： `key` がなければ `default` を返す
3) `try-except KeyError`：存在確認を伴う冗長だが柔軟な方法

```python
# 【1】辞書データ（部分的にキーが欠けている）
user_data = {
    "name": "Alice",
    "age": 30,
    # "email" キーは存在しない
}

# 【2】.get() を使って安全にアクセス
name = user_data.get("name", "不明")
email = user_data.get("email", "メール未登録")

print(f"名前: {name}")
print(f"メール: {email}")

# 【3】try/except を使った補完的ハンドリング
try:
    age = user_data["age"]
    print(f"年齢: {age}")
except KeyError:
    print("年齢情報が見つかりませんでした")
```
```
# 実行結果
名前: Alice
メール: メール未登録
年齢: 30
```

# Q.10-5
**リストの一部要素を変更しながら `for` 文で処理する際、`enumerate()` を使ってインデックスを活用するコードを記述せよ。**

### `enumerate()` の役割
`enumerate(iterable)` は、イテラブル(リストなど)を `(index, value)` のペアとしてループできる。`for`i, val in enumerate(list):` のように用いることで、リストと位置(インデックス)を同時に取得できる。

```python
for i, val in enumerate(["a", "b", "c"]):
    print(i, val)
# → 0 a, 1 b, 2 c
```
通常の `for val in list:` の形式では、その位置にある値なのかが分からず、リストの要素を直接変更できない。インデックスが必要な際は `enumerate()` を用いるのがPythonicな方法。

### 解答例とコードによる実践
リスト内の偶数だけ2倍に変更することを考える。

```python
# 【1】処理対象のリストを作成する
numbers = [1, 2, 3, 4, 5, 6]  # 【1】リストの初期化（1から6まで）

# 【2】enumerate() を使ってインデックスと値を同時に取得しながらループする
for i, value in enumerate(numbers):  # 【2】i: インデックス（0から開始）、value: 各要素

    # 【3】偶数かどうかを判定する
    if value % 2 == 0:  # 【3】値が偶数かどうか判定

        # 【4】偶数なら、その位置の値を2倍に変更する
        numbers[i] = value * 2  # 【4】偶数であれば、リストの値を2倍にする

# 【5】変更後のリストを表示して結果を確認する
print(numbers)  # 【5】変更後のリストを出力（[1, 4, 3, 8, 5, 12]）

# 【6】インデックス付きでリストの要素を表示
for i, val in enumerate(numbers, start=1):  # 【6】インデックスを1から開始
    print(f"{i}番目の値: {val}")  # 【7】インデックスと値を出力（1番目から表示）
```
```python
[1, 4, 3, 8, 5, 12]
1番目の値: 1
2番目の値: 4
3番目の値: 3
4番目の値: 8
5番目の値: 5
6番目の値: 12
```
リスト内包表記を用いて一気に書き上げることも可能。
```python
# 【1】元のリスト
numbers = [1, 2, 3, 4, 5, 6]

# 【2】リスト内包表記を使って、偶数を2倍に、奇数はそのままにして新しいリストを作る
modified_numbers = [num * 2 if num % 2 == 0 else num for num in numbers]

# 【3】結果を表示
print(modified_numbers)
```

## 【補足】
### 【1】`enumerate()` と `zip()` を用いた複数リストの同時処理とインデックス活用

`zip()` を用いれば複数リストの要素を同時に処理可能で、`enumerate()` を組み合わせると、インデックス(何番目か)も取得できる。これにより、「複数のリストの同じ位置にある要素をまとめて処理したい」場面に対応できる。
```python
for index, (x, y) in enumerate(zip(list1, list2)):
    # index: 位置、x: list1の値、y: list2の値
```

### コードによる実践
#### 【A】英語名とスコアのリストを同時処理し、位置付きで出力
```python
# 【1】名前リストの作成
names = ["Alice", "Bob", "Charlie"]  # 【1】

# 【2】得点リストの作成
scores = [85, 90, 78]  # 【2】

# 【3】enumerate + zip を使って同時に処理（インデックス付き）
for i, (name, score) in enumerate(zip(names, scores), start=1):  # 【3】
    # 【4】インデックス付きで名前とスコアを表示
    print(f"{i}番目: {name}さんの得点は {score} 点です。")  # 【4】
```
```python
1番目: Aliceさんの得点は 85 点です。
2番目: Bobさんの得点は 90 点です。
3番目: Charlieさんの得点は 78 点です。
```

#### 【B】複数のリストの要素を同時に加工
```python
# 【1】商品名のリスト
products = ["apple", "banana", "carrot"]  # 【1】

# 【2】価格リストの作成
prices = [120, 80, 60]  # 【2】

# 【3】zip + enumerate を使って同時処理
for i, (name, price) in enumerate(zip(products, prices)):  # 【3】
    # 【4】価格が100未満のものをフィルタ
    if price < 100:  # 【4】
        # 【5】商品名を大文字で出力
        print(f"{i}: {name.upper()} is affordable at {price} yen.")  # 【5】
```
```python
1: BANANA is affordable at 80 yen.
2: CARROT is affordable at 60 yen.
```

#### 【C】条件を満たす要素だけリスト内で直接変更(インプレース更新)
```python
# 【1】元のリスト a（更新対象）
a = [1, 2, 3, 4, 5]  # 【1】

# 【2】リスト b（条件判定に使う）
b = [10, 20, 30, 40, 50]  # 【2】

# 【3】zip で2つのリストを同時に処理（インデックスも取得）
for i, (_, val_b) in enumerate(zip(a, b)):  # 【3】
    # 【4】条件：b の値が 30 以上
    if val_b >= 30:  # 【4】
        # 【5】条件を満たした a の要素を2倍に変更
        a[i] *= 2  # 【5】

# 【6】変更後のリスト a を表示
print(a)  # 【6】
```
```python
[1, 2, 6, 8, 10]
```

# Q.10-6
`for` 文で構築された処理を `map()` や `functools.reduce()` などの高階関数で書き換える場合の利点と制約を論ぜよ。

### 問題背景
#### 【1】`for` 文による処理
`for` 文は、反復可能オブジェクト(リスト、タプルなど)の各要素に対して反復的な処理を行う構文である。反復処理を簡潔に書けるため、多くのPythonプログラマーが直観的に使用する。

#### 【2】高階関数(`map()` と `reduce()`)
`map(function, iterable)` は指定された関数をイテラブルの各要素に適用し、新しいリスト(またはイテラブル)を返す。特に、`map()` は、与えられた関数をリストの各要素に適用し、結果を新しいリストにまとめる。
`reduce(function, iterable)` は、イテラブルの全ての要素を累積的に処理する。最初の2つの要素を処理した結果と次の要素を組み合わせ、最後に1つの結果を返す。
特に、`reduce()` は関数を適用して要素を集約するのにつかわれる。

#### 【3】`for` 文と高階関数の違い
`for` 文は明示的に要素を取り出して処理を行い、必要に応じて値を変更したり、リストに追加する操作が可能。一方で、`map()` や `reduce()` は関数型プログラミングスタイルに基づいており、明示的に反復を扱うことなく、関数に適応することに焦点を当てる。

#### 【4】高階関数の利点と制約
**【利点】**
・ 簡潔さと可読性の高さ：特に `map()` は、簡単に書けるのでコードが短くなり、読みやすくなる。
・ 関数型プログラミング：Pythonでは関数型プログラミングスタイルを活用でき、可読性や再利用性を向上させることが出来る
・ 効率性：`map()` や `reduce()` は、Cのような低レベルの言語で実装されているため、パフォーマンスがいいことが多い
**【制約】**
・ 直観的でない：関数型スタイルに慣れていないければ、`map()` や `reduce()` の使用は直感的でなく、特に複雑な処理の場合は読みづらくなることがある
・ 副作用が扱いづらい：`map()` や `reduce()` は関数型プログラミングに基づくため、副作用が少ないこと(関数がほかの変数を変更しない)ことを想定している。副作用が伴う処理には不向き。

### 解答例とコードによる実践
`map()` は各要素に関数を適用し、新しいリストを作成するので、反復処理が簡潔になる。また、`reduce()` はリストのすべての要素を集約して1つの結果を返すため、合計、積算、または累積処理に便利です。

```python
# 【1】元のリスト（数値リスト）
numbers = [1, 2, 3, 4, 5]

# 【2】for 文でリストの各要素を2倍にして新しいリストを作成
doubled_for = []
for num in numbers:  # 【3】元のリストの各要素に対して反復処理
    doubled_for.append(num * 2)  # 【4】各要素を2倍にして新しいリストに追加

print("for文の結果:", doubled_for)  # 【5】結果を表示

# 【6】map() を使った同じ処理
doubled_map = list(map(lambda x: x * 2, numbers))  # 【7】map関数を使ってリストの各要素を2倍にする
print("map()の結果:", doubled_map)  # 【8】結果を表示

# 【9】reduce() を使って合計を計算
from functools import reduce
sum_result = reduce(lambda x, y: x + y, numbers)  # 【10】reduceで累積的に足し合わせる
print("reduce() の結果（合計）:", sum_result)  # 【11】合計結果を表示
```
```python
for文の結果: [2, 4, 6, 8, 10]
map()の結果: [2, 4, 6, 8, 10]
reduce() の結果（合計）: 15
```
## 【補足】
【1】 `filter()` と `sorted()`
`filter()` は、指定した条件に一致する要素だけを抽出する関数。第1引数には条件を判定する関数、第2引数にはイテラブル(リスト、タプルなど)を渡す。条件に一致する要素だけが返されるという特徴がある。

```python
filter(function, iterable)
```
例として以下。
```python
numbers = [1, 2, 3, 4, 5, 6]
result = filter(lambda x: x % 2 == 0, numbers)  # 偶数だけを抽出
print(list(result))  # [2, 4, 6]
```

`sorted()` は、イテラブルを並べ替えた新しいリストを返す関数。昇順に並ぶが、`reverse=True` を指定することで降順に変更できる。

```python
sorted(iterable, key=None, reverse=False)
```
`key` ：並び替えの基準となる関数を指定。通常、指定しなければ値そのもので並べ替える。`reverse: True` にすると降順、`False` で昇順になる(デフォルトは `False`)。
```python
numbers = [5, 3, 8, 1, 4]
result = sorted(numbers)  # 昇順
print(result)  # [1, 3, 4, 5, 8]
```

### 高階関数の組み合わせ(コードによる実践)
#### 【A】`filter()` と `sorted()` の組み合わせ
特定範囲の偶数を抽出することを考える。
```python
# 【1】名前とスコアのリスト
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 92},
    {"name": "Charlie", "score": 78},
    {"name": "David", "score": 95}
]

# 【2】スコアが80以上の学生を抽出し、スコアが高い順に並べ替える
filtered_sorted = sorted(
    filter(lambda x: x["score"] >= 80, students),  # 【3】スコアが80以上の学生を抽出
    key=lambda x: x["score"],  # 【4】スコアで並べ替え
    reverse=True  # 【5】高い順に並べ替える
)

# 【6】結果を表示
for student in filtered_sorted:
    print(f"{student['name']} の得点は {student['score']} 点です。")
```
```python
David の得点は 95 点です。
Bob の得点は 92 点です。
Alice の得点は 85 点です。
```

#### 【B】`map()` で全要素を変換し、filter() で特定の条件を満たすものを抽出する

```python
# 【1】名前と年齢のリスト
people = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35},
]

# 【2】年齢が30歳以上の人を名前だけに変換して抽出
result = list(map(lambda person: person["name"], filter(lambda person: person["age"] >= 30, people)))

# 【3】結果を表示
print(result)  # 【4】['Alice', 'Charlie']
```

# あとがき
今回は「 `for` によるループ処理」について扱いました。個人的にはもっと扱いたかった問題も多いのですが、盲点となりがちな話や重要そうに感じる話題を中心に選んでみました。次回は、「`range` 関数とループ処理」を扱います。

# 参考文献
[1] 独習Python (2020, 山田祥寛, 翔泳社)
[2] Pythonクイックリファレンス 第4版（2024, Alex, O’Reilly Japan）
[3] 【Python 猛特訓】100本ノックで基礎力を向上させよう！プログラミング初心者向けの厳選100問を出題！(Youtube, https://youtu.be/v5lpFzSwKbc?si=PEtaPNdD1TNHhnAG)