2.1.6 データ構造

このページを終えたら、この evidence card を残します。
- 概念
- 変数、型、演算子、入力/出力、分岐、ループ、構造、関数、またはモジュール
- コード
- この概念のための最小限の実行可能な Python スニペット
- 出力
- 印字値、型、branch結果、loop trace、または返り値
- 失敗確認
- 型不一致、インデント、オフバイワン、可変データ、または import パスの問題
- 期待される成果
- 概念が機能することを証明するコードと出力結果
この節の位置づけ
Section titled “この節の位置づけ”この節では、ひとまとまりのデータをどう整理するかを学びます。リスト、タプル、辞書、集合は、この先のクローラー、データ分析、機械学習のサンプル処理、API レスポンスの解析までずっと使います。大事なのは、それぞれの構造が何を入れるのに向いているか、どんなときにどれを使うかを知ることです。
- リスト(list)の作成と基本操作を理解する
- タプル(tuple)の特徴と使いどころを理解する
- 辞書(dict)のキー・バリュー操作を身につける
- 集合(set)の重複削除と集合演算を知る
- 場面に応じて適切なデータ構造を選べるようになる
なぜデータ構造が必要なの?
Section titled “なぜデータ構造が必要なの?”ここまで学んだ変数は、1つの値しか入れられません。でも実際の場面では、ひとまとまりのデータを扱うことがよくあります。
- 100回分の API レイテンシ
- あるモデルのすべてのパラメータ
- ユーザーの個人情報(名前、年齢、メールアドレス……)
データ構造は、複数のデータを整理して保存するための入れ物です。
Python には 4 種類の組み込みデータ構造があります。迷ったときは、まずこう選びます。
| 必要なこと | 使うもの |
|---|---|
| 順序があり、よく変更するデータ | リスト [] |
| 順序があり、変更したくないデータ | タプル () |
| 名前や ID で値を取り出す | 辞書 {key: value} |
| 重複を消す、または集合を比べる | 集合 {item} |
主な性質も押さえておきましょう。
- リスト: 順序あり、変更可能、重複可能。
- タプル: 順序あり、変更不可、重複可能。
- 辞書: 追加した順序を保つ、変更可能、キーは重複不可。
- 集合: 順序なし、変更可能、重複は自動で削除される。
リスト(list)—— 最もよく使うデータ構造
Section titled “リスト(list)—— 最もよく使うデータ構造”リストは、伸び縮みする引き出しのようなものです。中にいろいろなものを入れられて、いつでも追加・削除・変更できます。
リストを作る
Section titled “リストを作る”# リストを作成latencies_ms = [120, 95, 240, 180, 310]features = ["ログイン API", "RAG デモ", "グラフビュー"]mixed = [1, "hello", 3.14, True] # 型を混ぜることもできる(おすすめはしない)empty = [] # 空のリスト
print(type(latencies_ms)) # <class 'list'>print(len(latencies_ms)) # 5要素にアクセスする(インデックス)
Section titled “要素にアクセスする(インデックス)”service_queue = ["ログイン API", "検索 API", "Worker", "ダッシュボード", "ドキュメントサイト"]# 0 1 2 3 4# -5 -4 -3 -2 -1
print(service_queue[0]) # ログイン API(最初のサービス)print(service_queue[2]) # Worker(3番目のサービス)print(service_queue[-1]) # ドキュメントサイト(最後のサービス)print(service_queue[-2]) # ダッシュボード(後ろから2番目のサービス)numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # [2, 3, 4](インデックス 2 から 4)print(numbers[:3]) # [0, 1, 2](最初の3つ)print(numbers[7:]) # [7, 8, 9](インデックス 7 から最後まで)print(numbers[::2]) # [0, 2, 4, 6, 8](1つおきに取り出す)print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0](逆順)要素を変更する
Section titled “要素を変更する”latencies_ms = [120, 95, 240, 180, 310]
# 1つの要素を変更latencies_ms[2] = 210print(latencies_ms) # [120, 95, 210, 180, 310]
# 複数の要素を変更(スライスを使う)latencies_ms[1:3] = [100, 180]print(latencies_ms) # [120, 100, 180, 180, 310]要素を追加する
Section titled “要素を追加する”tasks = ["ログインフォームを作る", "API テストを書く"]
# 末尾に追加tasks.append("エラー状態を追加する")print(tasks) # ['ログインフォームを作る', 'API テストを書く', 'エラー状態を追加する']
# 指定した位置に挿入tasks.insert(1, "認証フローをレビューする")print(tasks) # ['ログインフォームを作る', '認証フローをレビューする', 'API テストを書く', 'エラー状態を追加する']
# 複数の要素を追加tasks.extend(["README を更新する", "デモを録画する"])print(tasks) # ['ログインフォームを作る', '認証フローをレビューする', 'API テストを書く', 'エラー状態を追加する', 'README を更新する', 'デモを録画する']要素を削除する
Section titled “要素を削除する”tasks = ["ログインフォームを作る", "API テストを書く", "エラー状態を追加する", "認証フローをレビューする", "デモを録画する"]
# 値で削除(最初に一致したものを削除)tasks.remove("エラー状態を追加する")print(tasks) # ['ログインフォームを作る', 'API テストを書く', '認証フローをレビューする', 'デモを録画する']
# インデックスで削除deleted = tasks.pop(1) # インデックス 1 の要素を削除して返すprint(deleted) # API テストを書くprint(tasks) # ['ログインフォームを作る', '認証フローをレビューする', 'デモを録画する']
# 最後の要素を削除last = tasks.pop()print(last) # デモを録画する
# インデックスで削除(返り値は不要)del tasks[0]print(tasks) # ['認証フローをレビューする']リストでよく使う操作
Section titled “リストでよく使う操作”numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5]
# ソートnumbers.sort()print(numbers) # [1, 1, 2, 3, 4, 5, 5, 6, 9]
# 降順ソートnumbers.sort(reverse=True)print(numbers) # [9, 6, 5, 5, 4, 3, 2, 1, 1]
# 元のリストを変更しないソートoriginal = [3, 1, 4, 1, 5]sorted_list = sorted(original)print(original) # [3, 1, 4, 1, 5](元のリストは変わらない)print(sorted_list) # [1, 1, 3, 4, 5]
# 反転numbers = [1, 2, 3, 4, 5]numbers.reverse()print(numbers) # [5, 4, 3, 2, 1]
# 検索print(numbers.index(3)) # 2(要素 3 のインデックス)print(numbers.count(5)) # 1(要素 5 の出現回数)print(3 in numbers) # True
# 集計latencies_ms = [120, 95, 240, 180, 310]print(len(latencies_ms)) # 5print(sum(latencies_ms)) # 945print(max(latencies_ms)) # 310print(min(latencies_ms)) # 95print(sum(latencies_ms) / len(latencies_ms)) # 189.0(平均レイテンシ)リスト内包表記(とても Python らしい書き方!)
Section titled “リスト内包表記(とても Python らしい書き方!)”リスト内包表記は、新しいリストを簡潔に作る方法です。
# ふつうの書き方squares = []for i in range(1, 6): squares.append(i ** 2)print(squares) # [1, 4, 9, 16, 25]
# リスト内包表記(一行で書ける!)squares = [i ** 2 for i in range(1, 6)]print(squares) # [1, 4, 9, 16, 25]
# 条件付きのリスト内包表記even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]print(even_squares) # [4, 16, 36, 64, 100]
# 実践例:機能 slug を正規化するraw_slugs = [" Login API ", "RAG DEMO", " Chart View "]clean_slugs = [slug.strip().lower().replace(" ", "-") for slug in raw_slugs]print(clean_slugs) # ['login-api', 'rag-demo', 'chart-view']タプル(tuple)—— 変更できないリスト
Section titled “タプル(tuple)—— 変更できないリスト”タプルとリストはほとんど同じですが、違いは1つです。タプルは作成後に変更できません。
タプルを作る
Section titled “タプルを作る”# 丸括弧で作成point = (3, 4)colors = ("赤", "緑", "青")single = (42,) # 要素が1つだけのときは、カンマが必要!empty = ()
# 実は丸括弧は省略できるcoordinates = 3, 4 # これもタプルprint(type(coordinates)) # <class 'tuple'>タプルの操作
Section titled “タプルの操作”colors = ("赤", "緑", "青", "黄", "紫")
# アクセス(リストと同じ)print(colors[0]) # 赤print(colors[-1]) # 紫print(colors[1:3]) # ('緑', '青')
# 走査for color in colors: print(color)
# 検索print(len(colors)) # 5print("赤" in colors) # Trueprint(colors.count("赤")) # 1print(colors.index("青")) # 2
# ただし変更はできない!# colors[0] = "黒" # エラー!TypeError: 'tuple' object does not support item assignmentタプルのアンパック
Section titled “タプルのアンパック”# タプルの値をそれぞれ別の変数に代入するpoint = (10, 20)x, y = pointprint(f"x={x}, y={y}") # x=10, y=20
# 関数が複数の値を返すとき、実際にはタプルが返っているdef get_task_and_hours(): return "ログイン API", 8
task, hours = get_task_and_hours()print(f"{task}, {hours}時間") # ログイン API, 8時間
# * を使って余った値をまとめるfirst, *rest = [1, 2, 3, 4, 5]print(first) # 1print(rest) # [2, 3, 4, 5]いつタプルを使う?
Section titled “いつタプルを使う?”- データを変更されたくないとき(例:座標、RGB の色の値)
- 辞書のキーに使いたいとき(リストは辞書のキーにできないが、タプルはできる)
- 関数で複数の値を返したいとき
辞書(dict)—— キー・バリューで保存する
Section titled “辞書(dict)—— キー・バリューで保存する”辞書は、Python のとても重要なデータ構造の1つです。キー(key) で 値(value) を探します。まるで実際の辞書で、単語から意味を引くようなものです。
# 波括弧で作成task = { "name": "ログイン API", "owner": "Mina", "status": "進行中", "hours": [2, 3, 3]}
# 空の辞書empty = {}
# dict() で作成config = dict(learning_rate=0.001, epochs=100, batch_size=32)print(config) # {'learning_rate': 0.001, 'epochs': 100, 'batch_size': 32}
print(type(task)) # <class 'dict'>値にアクセスする
Section titled “値にアクセスする”task = {"name": "ログイン API", "owner": "Mina", "status": "進行中"}
# 方法1:[] でアクセスprint(task["name"]) # ログイン API# print(task["deadline"]) # エラー!KeyError: 'deadline'
# 方法2:.get() でアクセス(より安全)print(task.get("owner")) # Minaprint(task.get("deadline")) # None(存在しないときは None を返し、エラーにならない)print(task.get("deadline", "未設定")) # 未設定(存在しないときはデフォルト値を返す)task = {"name": "ログイン API", "status": "未着手"}
# 新しいキー・バリューを追加task["owner"] = "Mina"task["repo"] = "portfolio-api"
# 既存の値を変更task["status"] = "進行中"
print(task)# {'name': 'ログイン API', 'status': '進行中', 'owner': 'Mina', 'repo': 'portfolio-api'}
# まとめて更新task.update({"status": "完了", "hours": 8})print(task)task = {"name": "ログイン API", "status": "完了", "owner": "Mina"}
# 指定したキーを削除del task["owner"]print(task) # {'name': 'ログイン API', 'status': '完了'}
# pop:削除して値を返すstatus = task.pop("status")print(status) # 完了print(task) # {'name': 'ログイン API'}辞書を走査する
Section titled “辞書を走査する”task_hours = {"ログイン API": 8, "RAG デモ": 12, "グラフビュー": 5}
# キーを走査for task in task_hours: print(task)
# 値を走査for hours in task_hours.values(): print(hours)
# キー・バリューを走査(最もよく使う)for task, hours in task_hours.items(): print(f"{task}: {hours} 時間")
# 出力:# ログイン API: 8 時間# RAG デモ: 12 時間# グラフビュー: 5 時間辞書内包表記
Section titled “辞書内包表記”# 数字から平方への対応表を作るsquares = {x: x**2 for x in range(1, 6)}print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 辞書を絞り込むtask_hours = {"ログイン API": 8, "バグ修正": 3, "RAG デモ": 12, "ドキュメント": 2}large_tasks = {name: hours for name, hours in task_hours.items() if hours >= 8}print(large_tasks) # {'ログイン API': 8, 'RAG デモ': 12}実践例:文字の出現回数を数える
Section titled “実践例:文字の出現回数を数える”text = "hello world"char_count = {}
for char in text: if char in char_count: char_count[char] += 1 else: char_count[char] = 1
print(char_count)# {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}集合(set)—— 重複削除の強い味方
Section titled “集合(set)—— 重複削除の強い味方”集合は、順序がなく、重複しない要素の集まりです。
# 波括弧で作成task_tags = {"api", "ui", "testing", "api"} # 重複は自動で削除されるprint(task_tags) # {'testing', 'ui', 'api'}(順序は変わることがある)
# リストから作成(重複を削除!)modules = ["api", "api", "ui", "worker", "ui", "db"]unique_modules = set(modules)print(unique_modules) # {'api', 'db', 'ui', 'worker'}(順序は変わることがある)
# 注意:空の集合は set() を使う。{} ではないempty_set = set() # 空集合empty_dict = {} # これは空の辞書!
print(type(task_tags)) # <class 'set'>a = {1, 2, 3, 4, 5}b = {4, 5, 6, 7, 8}
# 積集合(両方にあるもの)print(a & b) # {4, 5}print(a.intersection(b))
# 和集合(まとめて、重複なし)print(a | b) # {1, 2, 3, 4, 5, 6, 7, 8}print(a.union(b))
# 差集合(a にはあるが b にはないもの)print(a - b) # {1, 2, 3}print(a.difference(b))
# 対称差集合(それぞれにしかないもの)print(a ^ b) # {1, 2, 3, 6, 7, 8}実践での使い方
Section titled “実践での使い方”# 例:フロントエンドとバックエンドの両方に関わるタスクを探すfrontend_tasks = {"ログイン UI", "グラフビュー", "設定ページ", "テーマ切替"}backend_tasks = {"ログイン API", "グラフビュー", "監査ログ", "設定ページ"}
both = frontend_tasks & backend_tasksprint(f"両方に関わるタスク: {sorted(both)}")
only_frontend = frontend_tasks - backend_tasksprint(f"フロントエンドのみのタスク: {sorted(only_frontend)}")
all_tasks = frontend_tasks | backend_tasksprint(f"関連タスクすべて: {sorted(all_tasks)}")データ構造の選び方ガイド
Section titled “データ構造の選び方ガイド”| 目的 | おすすめ | 理由 |
|---|---|---|
| 順序のある集合で、追加・削除・変更をしたい | リスト | いちばん汎用的な入れ物 |
| データを変更したくない | タプル | 変更不可で安全 |
| キーで値を探したい | 辞書 | O(1) で高速に検索できる |
| 重複を取り除きたい | 集合 | 自動で重複を削除する |
| 出現回数を数えたい | 辞書 | キーを要素、値をカウントにできる |
| 要素が存在するか調べたい | 集合/辞書 | リストよりずっと速い |
練習 1:API レイテンシ集計
Section titled “練習 1:API レイテンシ集計”latencies_ms = [120, 95, 240, 180, 310, 150, 88, 205, 260, 170]
# 1. 最大レイテンシ、最小レイテンシ、平均レイテンシを計算する# 2. 200 ms を超えるレイテンシをすべて取り出す(リスト内包表記を使う)# 3. レイテンシを高い順に並べる練習 2:サービス担当者ディレクトリ
Section titled “練習 2:サービス担当者ディレクトリ”辞書を使って、簡単なサービス担当者ディレクトリを作ってみましょう。
owners = {}
# 1. 3つのサービスを追加する(サービス名 → 担当者メール)# 2. あるサービス担当者のメールを調べる# 3. あるサービス担当者のメールを変更する# 4. 1つのサービスを削除する# 5. すべてのサービス担当者を表示する練習 3:イベント語の出現回数を数える
Section titled “練習 3:イベント語の出現回数を数える”text = "api error api timeout worker error api"
# それぞれのイベント語が何回出てくるかを数える# ヒント:まず split() でリストに分けてから、辞書で数える練習 4:リストの重複削除(順序を保つ)
Section titled “練習 4:リストの重複削除(順序を保つ)”numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
# 重複を取り除くが、元の順序は保つ# 期待する出力: [3, 1, 4, 5, 9, 2, 6]# ヒント:すでに出てきた要素を集合で記録する参考実装と解説
- レイテンシの統計は、最大値
310、最小値88、平均181.8です。200ms 超は[240, 310, 205, 260]、降順では[310, 260, 240, 205, ...]から始まります。 - サービス担当者ディレクトリはキーで追加、更新、削除します。例:
owners["ログイン API"] = "[email protected]"。 - サンプルのイベント語頻度では、
apiが3、errorが2、timeoutとworkerが 1 回です。 - 順序を保った重複削除の結果は
[3, 1, 4, 5, 9, 2, 6]です。seenセットと結果リストを組み合わせます。 - 順序が必要ならリスト、検索なら辞書、所属判定や重複削除なら集合、固定レコードならタプルを選びます。
| データ構造 | 作り方 | 特徴 | よくある場面 |
|---|---|---|---|
| リスト | [1, 2, 3] | 順序あり、可変、重複可 | ひとまとまりの同種データを保存する |
| タプル | (1, 2, 3) | 順序あり、変更不可 | 座標、複数の戻り値 |
| 辞書 | {"a": 1} | キー・バリュー、キー重複不可 | 設定、対応関係 |
| 集合 | {1, 2, 3} | 順序なし、重複なし | 重複削除、集合演算 |