はじめに
リストの内包表記って、最初ぱっと見では全然わからないですよね。 僕も競プロの問題を解いたとき、解答コードで内包表記をよく目にするのですが、「うーん、直感的にわからんな〜」となって、結局慣れている for 文で書いてしまいます。
それでまた後から解答を見て、「あ〜、内包表記で書けばよかった」と後悔する……そんなことを繰り返していました。 いい加減しっかり整理したいなと思ったので、今回まとめてみることにします。 「自分も内包表記、実は苦手なんだよなあ」という人は、ぜひ一緒に見ていってください。
リストの内包表記って?
簡単に言うと、**「リスト生成を1行で簡潔に書ける、Python特有の便利構文」**のようなものです。 例えば、こんなコードがあります。
Python
squares = [x**2 for x in range(10)]
これ、ぱっと見ても最初は「?」となりますよね。 ということで、まずは「読み方」のルールを確認してみましょう。 構造を日本語で分けてみると、以下のようになります。
[ ① 出力する値(式) ② ループ(for) ③ 条件(if) ]
処理の流れをイメージすると、こんな感じです:
- **②(ループ)**から要素を1つずつ取り出す
- その要素を**③(条件)**に基づいて判定する
- 条件をパスしたら、その要素を**①(式)**の形に加工して値を決定する
- すべての要素に対して1〜3を行い、結果をリストに詰め込む
先ほどのコード例にあてはめてみます(今回は③の条件がないパターンです)。
range(10)なので0〜9の要素を1つずつ取り出す- その要素を2乗する(
**2 なので) - 全要素に対して1〜2を行った結果を、リストに内包する
なので、squares の内容は以下のようになります。
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
こうして日本語で手順を整理すると、少しわかりやすくなる気がしませんか?
いつもの書き方と比べてみる
次に、内包表記を使わない場合と比べて、どれくらいコードが変わるか見てみます。 「1〜10の数字から偶数だけを取り出す」という例で比較してみましょう。
【いつもの書き方(for文)】
numList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_list = []
for num in numList:
if num % 2 == 0:
even_list.append(num)
【リストの内包表記】
numList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_list = [num for num in numList if num % 2 == 0]
処理の流れだけいうと
- numListから要素を1つずつnumとして取り出す
- その要素を「if num % 2 == 0」の条件を基に判定
- その判定が通ったらnumとして値を決定
- 1~3を行った結果をリストに内包
となります。
4行かかっていた処理がたった1行にまとまり、だいぶスッキリしました!
わざわざ「空のリスト」を作って、append で追加するという手間が省けるので、コードの見通しが良くなります。
内包表記が「向いてなさそうな」パターン
「1行で書けるなら全部これでいいじゃん!」となりそうですが、逆に読みにくくなるので避けた方がいいパターンもあります。
ループが重なるとき
例えばこんな3重ループ。これ読みたくないですよね(笑)。
(そもそもパフォーマンス的に3重ループ自体の回避を考えたいですが)
# 3重ループを無理やり内包表記に
result = [x * y * z for x in range(5) for y in range(5) for z in range(5) if x > 0]
この場合は、素直に for 文を重ねて書いたほうが、構造がはっきりしてましな気がします。
result = []
for x in range(5):
for y in range(5):
for z in range(5):
if x > 0 and y > 0 and z > 0:
result.append(x * y * z)
条件(if)が多すぎるとき
条件が重なると、内包表記はどんどん右に長くなっていきます。実務だと「どこの条件に引っかかって除外されたのか」が追いにくく、デバッグも大変です。
# 条件が3つ以上重なると、解読が難しい
result = [x for x in data if x > 0 if x % 2 == 0 if x < 100]
これなら、以下のように内包表記を使わず、continue を使って「当てはまらないものはスキップ!」と門前払いしていく書き方のほうが、読みやすくて親切です。
(ほかにいい書き方があると思いますが今回は置いといて…)
result = []
for x in data:
if x <= 0: continue # 0以下ならスキップ
if x % 2 != 0: continue # 奇数ならスキップ
if x >= 100: continue # 100以上ならスキップ
result.append(x)
まとめ
言葉にして書き出してみたら、自分の中でも少し理解が深まった気がします。
コードがスッキリする便利な構文ですが、使いどころによっては逆に読みやすさを落としてしまうので、実務で使うならケースバイケースですね(笑)。
ただ、競プロでは頻出の書き方なので、覚えておいて損はないはずです。
読んでくださった方も、何となく「内包表記」のイメージを掴んでいただけていたらうれしいです。
ありがとうございました!
コメント