コンテナ比較と包括的な内包表記

リスト、タプル、辞書、セットを個別に学んできました。今回は横並びで比較します——それぞれの違いは何か、いつどれを使うべきか。また、Python の最も強力な機能の 1 つである内包表記も徹底的にマスターします。


1. 4 つのコンテナの比較

機能 リスト(list タプル(tuple 辞書(dict セット(set
構文 [] () {key: value} {} または set()
順序付け ✅ あり ✅ あり ✅ 挿入順(3.7+) ❌ なし
ミュータブル
重複 ✅ 許可 ✅ 許可 キーは一意、値は重複可能 ❌ ユニーク
アクセス インデックス [0] インデックス [0] キー ["key"] インデックス不可
検索速度 O(n) 低速 O(n) 低速 O(1) 非常に高速 O(1) 非常に高速
典型的な用途 順序データ、動的コレクション 固定データ、関数の戻り値 マッピング、キャッシュ 重複除去、集合演算
💡 重要な選択原則: 1)位置によるアクセスが必要 → リスト/タプル。2)名前によるアクセスが必要 → 辞書。3)重複除去や集合演算が必要 → セット。4)データが変更されない → タプル。

例:コンテナ選択の比較(難易度 ⭐⭐)

PYTHON
# 同じシナリオを異なるコンテナで実装

# シナリオ:学生名の保存
# リスト — 順序付けられ、追加/削除が容易
students_list = ["Alice", "Bob", "Charlie"]
students_list.append("Diana")
print(f"List: {students_list[0]}")    # Alice

# タプル — 固定(クラス名簿など)
students_tuple = ("Alice", "Bob", "Charlie")
# students_tuple[0] = "xxx"     # エラー!タプルはイミュータブル
print(f"Tuple: {students_tuple[0]}")   # Alice

# 辞書 — ID による高速検索
students_dict = {
    "001": "Alice",
    "002": "Bob",
    "003": "Charlie",
}
print(f"Dict: {students_dict['002']}")  # Bob — O(1) の速度

# セット — 重複除去(出席リストなど)
attendance = {"Alice", "Bob", "Charlie", "Alice"}
print(f"Set: {attendance}")            # {'Bob', 'Charlie', 'Alice'} — Alice は重複除去
▶ 試してみよう

2. 高度なリスト内包表記

レッスン 11 では基本的な内包表記を学びました。さらに深く掘り下げましょう。

複数条件

PYTHON
numbers = range(1, 31)

# 3 で割り切れ、かつ 5 で割り切れる
filtered = [n for n in numbers if n % 3 == 0 and n % 5 == 0]
print(filtered)             # [15, 30]

# 3 で割り切れる、または 5 で割り切れる
filtered = [n for n in numbers if n % 3 == 0 or n % 5 == 0]
print(filtered)             # [3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30]

ネストしたループの内包表記

PYTHON
# 座標ペアを生成 — 二重 for ループに相当
pairs = [(x, y) for x in range(3) for y in range(3)]
print(pairs)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

# 条件付きネスト — 和が偶数のペアを見つける
pairs = [(x, y) for x in range(3) for y in range(3) if (x + y) % 2 == 0]
print(pairs)
# [(0, 0), (0, 2), (1, 1), (2, 0), (2, 2)]

内包表記での if-else

PYTHON
# 内包表記での if-else — 位置が異なることに注意
numbers = [1, 2, 3, 4, 5, 6]

# if のみ(フィルタリング)— 後ろに配置
evens = [n for n in numbers if n % 2 == 0]

# if-else(変換)— 前に配置
result = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(result)   # ['odd', 'even', 'odd', 'even', 'odd', 'even']

例:データ変換パイプライン(難易度 ⭐⭐⭐)

PYTHON
# 1 行で:フィルタ → 変換 → フォーマット
raw = ["  apple  ", "BANANA", "", "  CHERRY", None, "  date  "]

# None と空文字列を除外 → 空白を除去 → 先頭大文字
cleaned = [item.strip().capitalize() for item in raw if item and item.strip()]
print(cleaned)              # ['Apple', 'Banana', 'Cherry', 'Date']
▶ 試してみよう

出力:

TEXT
['Apple', 'Banana', 'Cherry', 'Date']

3. 辞書内包表記

辞書でも内包表記が使えます。構文はリストと似ていますが、{} を使用し、キーと値のペアを指定します:

PYTHON
# 基本的な辞書内包表記
squares = {x: x ** 2 for x in range(5)}
print(squares)              # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# 条件付き
even_squares = {x: x ** 2 for x in range(10) if x % 2 == 0}
print(even_squares)         # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

# キーと値の反転
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {value: key for key, value in original.items()}
print(reversed_dict)        # {1: 'a', 2: 'b', 3: 'c'}

実践:2 つのリストを辞書にマージ

PYTHON
# 2 つのリストを辞書にマージ
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
result = {names[i]: scores[i] for i in range(len(names))}
print(result)               # {'Alice': 85, 'Bob': 92, 'Charlie': 78}

# zip() を使うとよりエレガント
result = {name: score for name, score in zip(names, scores)}
print(result)               # {'Alice': 85, 'Bob': 92, 'Charlie': 78}

4. セット内包表記

セット内包表記は {} を使って式のみを記述します(コロンはなし)——辞書内包表記との違いはコロンの有無です:

PYTHON
# セット内包表記 — 自動重複除去
numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)       # {1, 4, 9, 16, 25}

# 条件付き
even_set = {x for x in range(20) if x % 2 == 0}
print(even_set)             # {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
💡 3 つの区別: [x for x in ...] はリスト——順序付けられ、重複可能。{x: y for x in ...} は辞書——コロンあり。{x for x in ...} はセット——コロンなし、順序なし、重複除去。

例:単語分析(難易度 ⭐⭐⭐)

PYTHON
# 記事の語彙を分析
article = """
Python is great. Python is powerful!
I love Python. JavaScript is also great.
But Python is my favorite.
"""

# すべての単語を抽出
words = article.lower().split()

# セット内包表記で重複除去
unique_words = {word.strip(".!,") for word in words}
print(f"Total words: {len(words)}")
print(f"Unique words: {len(unique_words)}")
print(f"All unique words: {sorted(unique_words)}")
▶ 試してみよう

出力:

TEXT
Total words: 18
Unique words: 10
All unique words: ['also', 'but', 'favorite', 'great', 'is', 'javascript', 'love', 'my', 'python', 'i']

よくあるユースケース


❓ よくある質問

Q リスト内包表記とジェネレータ式の違いは何ですか?
A リスト内包表記 [x for x in range(10)] はすべてのデータを一度にメモリに生成します。ジェネレータ式 (x for x in range(10)) は遅延評価で、一度に 1 つの値——メモリを節約します。大規模データセット(数万以上)ではジェネレータを使用してください。
⚠️ Q:内包表記のネストが 2 レベルを超える場合はどうすればよいですか? A:2 レベルを超える内包表記は読みにくくなります。[x for xs in matrix for ys in xs for x in ys] のようなコードは一目ではほとんど理解できません。3 レベル以上必要な場合は、通常の for ループかヘルパー関数を使用してください。

Q どのコンテナを使うか、どうやって決めればよいですか?
A 3 つの質問:① ユニークなキーが必要か?→ 辞書。② 重複除去や集合演算が必要か?→ セット。③ データを変更する必要があるか?→ 変更するならリスト、しないならタプル。ほとんどのシナリオでは、リストと辞書で 90% のユースケースをカバーできます。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):リスト内包表記を使って、10 から 50 までの 7 で割り切れるか 7 を含む数をすべて生成してください。ヒントor で 2 つの条件を結合。

  2. 中級(難易度 ⭐⭐):2 つのリスト keys = ["name", "age", "city"]values = ["Alice", 25, "Beijing"] が与えられたとき、辞書内包表記を使ってマージしてください。ヒントzip(keys, values) でペアにする。

  3. 挑戦(難易度 ⭐⭐⭐):「データ分析プログラム」を書いてください。複数の文を含む文字列が与えられたとき、総単語数、ユニーク単語数、最も頻繁な 3 つの単語をカウントして出力します。ヒント:トークン化に split()、カウントに辞書、上位 3 つに sorted(dict.items(), key=lambda x: x[1], reverse=True)[:3]

100%