【自前python講座】内包表記

python-comprehension プログラミング
python-comprehension



自前の python 講座用資料です.
今回は,python の中で特にエレガントな(pythonic な/ python の特性を活かした)書き方ができる内包表記について紹介します.
内包表記を用いると for と if-else の処理が1ラインで書くことができます.
利点としては,コード量や行数が減るので労力が低減出来たり,処理が明確化出来たりします.
今回も最後に演習問題を作成してみましたので,プログラミングしてみましょう.

今回のコードは,こちらの github にも載せています.

https://github.com/KazutoMakino/PythonCourse/blob/main/004_comprehension/004_comprehension.ipynb

リスト内包表記: 単純な for 文の書き換え

[式 for 変数 in iterable]

リスト内包表記は上記のように書くことができますが,次の for 文と同じです.

変数 = []
for 値 in iterable:
    変数.append(式)

実際に,コードを書いてみます.

arr = [v+1 for v in range(10)]
arr

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

これを for 文とインデントブロックを用いて書くと,

arr = []
for v in range(10):
    arr.append(v+1)
arr

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

ということで,なんと,3 行分の処理が 1 行で実現できます.
ミニマルですね.

入れ子構造や組み込み関数も利用可能です.

arr = [
    [0, 1, 2],
    ["a", "b", "c", "dddddddddd"]
]
[v for vv in arr for v in vv]

# [0, 1, 2, 'a', 'b', 'c', 'dddddddddd']
arr1 = [0,1,2]
arr2 = [0,10,20]
[[v1, v2] for v1,v2 in zip(arr1, arr2)]

# [[0, 0], [1, 10], [2, 20]]
arr = ["q", "w", "e", "r", "t", "y"]
[[i,v] for i,v in enumerate(arr)]

# [[0, 'q'], [1, 'w'], [2, 'e'], [3, 'r'], [4, 't'], [5, 'y']]

多重の for 文も使えます.

[[v1, v2] for v1 in arr1 for v2 in arr2]

# [[0, 0], [0, 10], [0, 20], [1, 0], [1, 10], [1, 20], [2, 0], [2, 10], [2, 20]]
[v1 + v2 for v1 in arr1 for v2 in arr2]

# [0, 10, 20, 1, 11, 21, 2, 12, 22]​

タプルの内包表記: 単純な for 文の書き換え

タプルの内包表記の作成方法として,基本的にはリスト内包表記とほとんど同じですが,[] を単に () にするだけでは,以下のように generator が返されてしまいます.

(v for v in range(5))

# <generator object <genexpr> at 0x00000145B05F26D0>

タプルで内包表記を実現するためには,[] から tuple() に変更します.

tuple(v for v in range(5))

# (0, 1, 2, 3, 4)

集合の内包表記: 単純な for 文の書き換え

集合の内包表記の作成方法も,基本的にはリスト内包表記とほとんど同じで,[] を単に {} にするだけです.

{int(v / 10) for v in range(0, 100, 1)}

# {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

辞書の内包表記: 単純な for 文の置き換え

辞書の内包表記もリスト内包表記と同じように書くことで実現できますが,キーと値といった 2 つの項目について処理する必要があるので,注意が必要です.
以下に例を示します.

key_arr = ["a", "b", "c"]
value_arr = ["A", "B", "C"]
{k:v for k,v in zip(key_arr, value_arr)}

# {'a': 'A', 'b': 'B', 'c': 'C'}
kv_list = [["a", "A"], ["b", "B"], ["c", "C"]]
{k:v for k,v in kv_list}

# {'a': 'A', 'b': 'B', 'c': 'C'}
{"a"*v:i for i,v in enumerate(range(3, 3+5, 1))}

# {'aaa': 0, 'aaaa': 1, 'aaaaa': 2, 'aaaaaa': 3, 'aaaaaaa': 4}
{key_arr[i]: value_arr[i] for i in range(len(key_arr))}

# {'a': 'A', 'b': 'B', 'c': 'C'}
{"a"*(v): v for v in range(1,1+5,1)}

# {'a': 1, 'aa': 2, 'aaa': 3, 'aaaa': 4, 'aaaaa': 5}

例えば上記のように,

  • 2 つのリストを zip() でまとめ,タプルのアンパック先を辞書のキーと値にする
  • 1 つのリストでもアンパック先が 2 つ以上であれば,それらを辞書のキーと値にする
  • 配列番号を表す 1 つのリストを for で繰り返し処理させ,元から用意してあった 2 つのリストに配列番号として渡し,それらを辞書のキーと値にする
  • その他,無理やり 2 つの値を作り,それらを辞書のキーと値にする

これらの方法によって,辞書の内包表記ができます.

内包表記: for 文の中に if 文で条件分岐する場合

タプル/集合/辞書の内包表記については,リスト内包表記とほとんど同じような考え方なので,以下,リストのみ取り扱います.

内包表記は,for の中に if があるような処理も扱うことができます.

[式 for 変数 in iterable if 条件式]

上記のように書くことができますが,次の for / if の処理と同じです.

変数 = []
for 値 in iterable:
    if 条件式:
        変数.append(式)

例として,

[v**2 for v in range(10) if v%2==1]

# [1, 9, 25, 49, 81]
a = ["banana", "apple", "orange"]
b = ["orange", "tomato", "apple"]
[v for v in a if v in b]

# ['apple', 'orange']

内包表記: for 文の中に if-else 文がある場合

for の中に if-else があるような処理も扱うことができます.
if のみを使う場合と異なり,少し癖があります.

[条件式が真の時の値 if 条件式 else 条件式が偽の時の値 for 変数 in iterable]

上記のように書くことができますが,次の for / if-else の処理と同じです.

変数 = []
for 値 in iterable:
    if 条件式:
        変数.append(条件式が真の時の値)
    else:
        変数.append(条件式が真の時の値)

例としては,

["even" if v%2==0 else "odd" for v in range(10)]

# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
[[v, "even"] if v%2==0 else [v, "odd"] for v in range(10)]

# [[0, 'even'],
#  [1, 'odd'],
#  [2, 'even'],
#  [3, 'odd'],
#  [4, 'even'],
#  [5, 'odd'],
#  [6, 'even'],
#  [7, 'odd'],
#  [8, 'even'],
#  [9, 'odd']]

以上が内包表記の書き方と例に対する紹介です.

今回も演習問題を用意いたしました.

演習問題

  • Q.1: 内包表記を用いた1つの処理式で,1 から 1000 までの 5 の倍数の総和を求めてください.
  • Q.2: 内包表記を用いた1つの処理式で,1 から 1000 まで連続した整数の内,7 と 13 の倍数に 1 足した値のみを加算してください(% を用います).
  • Q.3: 内包表記を用いた1つの処理式で,0 から 100 まで連続した整数の2乗根を要素とする,要素数 101 の次のリスト \([\sqrt{0}, \sqrt{1}, \sqrt{2}, \cdots , \sqrt{100}]\) を作成してください.
  • Q.4: 内包表記を用いた1つの処理式で,1 から 100 まで連続した 100 個の整数に対する標準偏差を求めてください.ここで,データの個数を \(n\), i番目のデータを \(x_i\), データの平均を \(\bar{x}\) とすると,標準偏差 \(\sigma\) は,

$$
\sigma = \sqrt{\frac{1}{n} \sum_{i=1}^n (x_i – \bar{x})^2}
$$

  で計算されます.

演習問題の解答

  • Q.1: 内包表記を用いた1つの処理式で,1 から 1000 までの 5 の倍数の総和を求めてください.
sum([v for v in range(5,1001,5)])

# 100500
  • Q.2: 内包表記を用いた1つの処理式で,1 から 1000 まで連続した整数の内,7 と 13 の倍数に 1 足した値のみを加算してください(% を用います).
sum([v for v in range(1,1001) if (v%7==1) or (v%13==1)])

# 104313
  • Q.3: 内包表記を用いた1つの処理式で,0 から 100 まで連続した整数の2乗根を要素とする,要素数 101 の次のリスト \([\sqrt{0}, \sqrt{1}, \sqrt{2}, \cdots , \sqrt{100}]\) を作成してください.
arr = [v**(1/2) for v in range(101)]
len(arr), arr[-1]

# (101, 10.0)
  • Q.4: 内包表記を用いた1つの処理式で,1 から 100 まで連続した 100 個の整数に対する標準偏差を求めてください.
(sum([ (v - sum(list(range(1,101)))/100)**2 for v in range(1,101)]) / 100) ** (1/2)

# 28.86607004772212

Python のおすすめの学習方法

プログラミングを最短で習得する,少なくても自分の意志で使えるようになる方法について,いくつかプログラミング言語を触ってきた筆者としては何の言語においても,以下2点が重要だと思います.

  • 元々自分が他の言語で作っていた処理を違う言語で書き直す・・・・英語を勉強するときも,脳を生まれたばかりのまっさらな状態から勉強するわけではなく,日本語を通したり対比して,学習済みの言語野を用いて勉強するのと似ています
  • 言語自体を網羅的に勉強するのではなく,やりたい事を先に考え,それを達成するために色々と調べながら実装する・・・・例えば,留学で語学力が上達するのは,その国の言葉を使ってコミュニケーションを取ることが強制されるためであり,使うことに対するモチベーションが一番大事です

独学で行うには,やはり2点目の「やりたい事ドリブン学習」が効果的で,例えば次の書籍は,Python を流行らせている AI/データ分析/機械学習/深層学習について実装することに主眼を置き説明されているので,実際に手を動かしながら学んでいける本だと思います(筆者も最初にこちらの書籍で遊びながら学びました).

コメント

タイトルとURLをコピーしました