こんにちは!前回はリスト内包表記の基本を解説しましたが、今回はさらに一歩進んで「ネストしたリスト内包表記」について詳しく解説します。この技術をマスターすれば、複雑なデータ処理も簡潔なコードで実現できるようになりますよ!
1. ネストしたリスト内包表記とは?
ネストしたリスト内包表記とは、リスト内包表記の中にさらにfor文を入れ込む技術です。これにより、多次元リスト(行列)の処理や、複数の繰り返し処理を1行で実現できます。
まずは基本的な構文を見てみましょう:
[式 for 変数1 in イテラブル1 for 変数2 in イテラブル2]
これは以下の入れ子になったfor文と同じ動作をします:
result = []
for 変数1 in イテラブル1:
for 変数2 in イテラブル2:
result.append(式)
2. 2次元リスト(行列)の平坦化
最もよく使われる例の一つが、2次元リストを1次元に「平坦化」する処理です:
# 営業部の月別売上データ(2次元リスト)
sales_data = [
[100, 150, 200], # 1Q(1月, 2月, 3月)
[120, 180, 210], # 2Q(4月, 5月, 6月)
[90, 160, 230], # 3Q(7月, 8月, 9月)
[200, 210, 180] # 4Q(10月, 11月, 12月)
]
# すべての売上を1つのリストに平坦化
all_sales = [amount for quarter in sales_data for amount in quarter]
print(all_sales)
# [100, 150, 200, 120, 180, 210, 90, 160, 230, 200, 210, 180]
実務での活用場面:四半期ごとにまとめられた売上データから年間の売上傾向を分析する際に、すべてのデータを一列に並べて統計処理するときに便利です。
3. ネストしたリスト内包表記の読み方
ネストしたリスト内包表記は、左から右に読むのではなく、普通のfor文と同じ順序で読むと理解しやすくなります:
[式 for 変数1 in イテラブル1 for 変数2 in イテラブル2]
for 変数1 in イテラブル1
(外側のループ)for 変数2 in イテラブル2
(内側のループ)式
(各要素に対する操作)
例えば、こんな例を見てみましょう:
# 部署と社員の組み合わせを作成
departments = ["営業部", "総務部", "開発部"]
employees = ["田中", "佐藤"]
# すべての組み合わせを作成
assignments = [f"{dept}の{emp}" for dept in departments for emp in employees]
print(assignments)
# ['営業部の田中', '営業部の佐藤', '総務部の田中', '総務部の佐藤', '開発部の田中', '開発部の佐藤']
実務での活用場面:組織改編時の人員配置シミュレーションや、シフト作成時の全パターン生成などに活用できます。
4. 条件を追加したネストの例
ネストしたリスト内包表記にも条件を追加できます:
# 商品と店舗の在庫データ
inventory = [
{"store": "東京店", "items": [("りんご", 50), ("バナナ", 20), ("オレンジ", 0)]},
{"store": "大阪店", "items": [("りんご", 30), ("バナナ", 0), ("オレンジ", 15)]},
{"store": "名古屋店", "items": [("りんご", 0), ("バナナ", 10), ("オレンジ", 25)]}
]
# 在庫切れ商品のリスト
out_of_stock = [(store["store"], item[0])
for store in inventory
for item in store["items"]
if item[1] == 0]
print(out_of_stock)
# [('東京店', 'オレンジ'), ('大阪店', 'バナナ'), ('名古屋店', 'りんご')]
実務での活用場面:複数店舗の在庫管理システムで、在庫切れ商品を素早く抽出し、発注リストを作成する際に便利です。
5. 多重ネストの例(3段階以上)
3段階以上のネストも可能ですが、可読性が低下するため注意が必要です:
# 年・四半期・月ごとの売上データ
yearly_data = [
{"year": 2020, "quarters": [
{"quarter": "Q1", "months": [("Jan", 100), ("Feb", 150), ("Mar", 200)]},
{"quarter": "Q2", "months": [("Apr", 120), ("May", 180), ("Jun", 210)]}
]},
{"year": 2021, "quarters": [
{"quarter": "Q1", "months": [("Jan", 110), ("Feb", 160), ("Mar", 210)]},
{"quarter": "Q2", "months": [("Apr", 130), ("May", 190), ("Jun", 220)]}
]}
]
# 各月の売上が150以上のデータだけ抽出
high_sales = [(year["year"], quarter["quarter"], month[0], month[1])
for year in yearly_data
for quarter in year["quarters"]
for month in quarter["months"]
if month[1] >= 150]
print(high_sales)
# [(2020, 'Q1', 'Feb', 150), (2020, 'Q1', 'Mar', 200), ... (省略)]
実務での活用場面:複雑な階層構造を持つデータから特定条件を満たすデータだけを抽出するレポート作成時に役立ちます。
6. ネストしたリストの生成
リスト内包表記のネストを使って、新しい2次元リストを生成することもできます:
# 9x9の掛け算表を生成
multiplication_table = [[i * j for j in range(1, 10)] for i in range(1, 10)]
# 最初の3行だけ表示
for row in multiplication_table[:3]:
print(row)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# [2, 4, 6, 8, 10, 12, 14, 16, 18]
# [3, 6, 9, 12, 15, 18, 21, 24, 27]
ここで重要なのは、最も外側のループが最も外側のリストに対応するということです。この例では:
[[式 for 変数2 in イテラブル2] for 変数1 in イテラブル1]
と書かれており、外側のループ(for i in range(1, 10)
)が行を生成し、内側のループ(for j in range(1, 10)
)が各行の要素を生成しています。
実務での活用場面:クロス集計表やシミュレーション結果のマトリックスを効率的に生成する際に使えます。
7. 辞書の内包表記とのネスト
リスト内包表記と辞書内包表記を組み合わせることもできます:
# 部署ごとの社員リスト
dept_data = [
{"name": "営業部", "members": ["田中", "佐藤", "鈴木"]},
{"name": "開発部", "members": ["高橋", "伊藤"]},
{"name": "総務部", "members": ["山本", "中村", "小林"]}
]
# 社員→部署の逆引き辞書を作成
employee_dept = {member: dept["name"]
for dept in dept_data
for member in dept["members"]}
print(employee_dept)
# {'田中': '営業部', '佐藤': '営業部', '鈴木': '営業部', '高橋': '開発部', ... (省略)}
実務での活用場面:組織データの逆引き検索や、データの再構成が必要なときに役立ちます。
8. 可読性とのバランス
ネストしたリスト内包表記は強力ですが、複雑になりすぎると可読性が低下します。以下のガイドラインを参考にしましょう:
- ネストは最大2段階まで:3段階以上になると理解が難しくなります
- 長い表現は複数行に分割:
\
で改行するか、括弧内で改行します - 複雑な処理は通常のループに:必ずしも1行で書く必要はありません
例えば、長い表現を複数行に分割する例:
# 複数行に分割したリスト内包表記
high_sales = [
(year["year"], quarter["quarter"], month[0], month[1])
for year in yearly_data
for quarter in year["quarters"]
for month in quarter["months"]
if month[1] >= 150
]
9. デバッグのコツ
ネストしたリスト内包表記のデバッグは難しいことがあります。以下のコツを覚えておきましょう:
- 段階的に構築:まず単純なリスト内包表記を作り、徐々にネストを追加
- 通常のループに戻す:問題が発生したら、通常のfor文に戻して確認
- 中間結果の確認:処理の途中経過を確認するために、一時的に通常のループを使用
10. 実務での活用例
最後に、実際の業務でよく使われるネストしたリスト内包表記の例をもう少し見てみましょう:
例1:CSVデータの前処理
# CSVから読み込んだデータ(数値が文字列として保存されている)
csv_data = [
["商品A", "100", "150", "200"],
["商品B", "120", "80", "90"],
["商品C", "50", "60", "70"]
]
# 1列目以外を数値に変換
processed_data = [[row[0]] + [int(cell) for cell in row[1:]] for row in csv_data]
print(processed_data)
# [['商品A', 100, 150, 200], ['商品B', 120, 80, 90], ['商品C', 50, 60, 70]]
実務での活用場面:エクセルやCSVから読み込んだデータの型変換や整形処理で使えます。
例2:複数条件での絞り込み
# 社員の勤怠・残業データ
attendance_data = [
{"name": "田中", "days": [
{"date": "2023-02-01", "attendance": True, "overtime": 2},
{"date": "2023-02-02", "attendance": True, "overtime": 0},
{"date": "2023-02-03", "attendance": False, "overtime": 0}
]},
{"name": "佐藤", "days": [
{"date": "2023-02-01", "attendance": True, "overtime": 1},
{"date": "2023-02-02", "attendance": True, "overtime": 3},
{"date": "2023-02-03", "attendance": True, "overtime": 2}
]}
]
# 出勤かつ残業2時間以上の記録を抽出
overtime_records = [
(employee["name"], day["date"], day["overtime"])
for employee in attendance_data
for day in employee["days"]
if day["attendance"] and day["overtime"] >= 2
]
print(overtime_records)
# [('田中', '2023-02-01', 2), ('佐藤', '2023-02-02', 3), ('佐藤', '2023-02-03', 2)]
実務での活用場面:勤怠管理システムからの残業報告書作成や、特定条件を満たす勤務状況の把握に役立ちます。
まとめ:ネストしたリスト内包表記のメリット・デメリット
メリット
- 複雑なデータ処理を簡潔に記述できる
- 多次元データの操作が直感的に書ける
- 処理速度が通常のループより若干速い
デメリット
- 複雑になると可読性が低下する
- デバッグが難しくなることがある
- チーム内の初心者には理解しづらいことがある
ネストしたリスト内包表記は、適切に使えば非常に強力なツールになります。しかし、「書ける」ことと「書くべき」ことは違います。常に可読性とのバランスを考えて、最適な場面で活用しましょう。