内包表記は、for文でlistを作成する際に便利な表記法です。また条件式を使うことで、if~else文を1行で表記することが可能となります。
これらは for文や if文がこれまでとは異なった形で記述されるので、初めは取っ付きにくいですが慣れれば便利な道具となると思います。
list 内包表記
例題_4_1 内包表記 (for)
例えば、y = (x – 4)**2 の式が与えられ、x が range(10) つまり0, 1, … , 9 の時のyのlistを lyに格納してみましょう。まず、内包表記を使わない方法でコードを書いてみます。
ly = []
for x in range(10):
ly.append((x - 4)**2)
ly
[16, 9, 4, 1, 0, 1, 4, 9, 16, 25]
通常の方法ですと、この様に最初に ly の初期値 [] を設定して、for ループの処理文にappendメソッドを使って(x – 4)**2 を追加していく形になります。
内包表記を使うと最初の3行を、初期値設定やappendメソッドを使わずに1行で表記することができます。
ly = [(x - 4)**2 for x in range(10)]
ly
[16, 9, 4, 1, 0, 1, 4, 9, 16, 25]
内包表記の書き方は、listを表す四角括弧 [ ] のなかに式を表す (x – 4)**2 を記載します。スペースを挟んだその後に、for x in range(10) と記載します。すると range(10) のindex順に取り出した x に対応した (x – 4)**2 のlistが作成されます。
内包表記は慣れるのが1番の近道ですので、さっそく練習問題に移ります。
練習問題_4_1 内包表記 (for)
lx = [-4, -1, 0, 1, 4] のときに以下のlistを作成しましょう。ヒントはありません。
- lx の各要素 x の2乗 を要素にもつlist ly1を作成
- lx の各要素 x の絶対値 abs(x) を要素にもつlist ly2を作成
- lx の各要素 x に対応するtuple (x, x**2) を要素にもつlist ly3を作成
練習問題_4_2 内包表記 (for) 素数の差分
練習問題_3_11(素数list)で作成した関数 prime_num を使用しますので、prime_numの関数を定義したコードをまず実行して下さい。そして lpn = prime_num(100000, 100100) で 100000 から 100100までの素数を lpn に格納して下さい。
ここから問題ですが、lpn に格納順の各素数の差のlistを内包表記を使って作成しlpdと言う変数に格納し、lpnとlpdを出力して下さい。
例えば、lpn が [1009, 1013, 1019, 1021] とすると lpd は 各要素の差
[1013 – 1009, 1019 – 1013, 1021 – 1019] → [4, 6, 2] となります。
これもヒント無しでお願いします。
例題_4_2 内包表記 (for + if)
内包表記にifにより条件を追加することができます。前記例題3_9の例にyが10より小さい要素のみでlist化するという条件を追加してみます。内包表記を使わない通常の方法では以下のように表せます。
ly = []
for x in range(10):
if (x - 4)**2 < 10:
ly.append((x - 4)**2)
ly
[9, 4, 1, 0, 1, 4, 9]
内包表記を使うとif文も1行の中に取り込んで表記することができます。
ly = [(x - 4)**2 for x in range(10) if (x - 4)**2 < 10]
ly
[9, 4, 1, 0, 1, 4, 9]
書き方は、for x in range(10)の後にスペースを入れてif (x – 4)**2 < 10を追加するだけです。ただしforとifの順番は変えられません。if文を先にするとSyntaxErrorとなります。
練習問題_4_3 内包表記 (for + if)
はじめに、sentence = ‘Hello World!’ とsentenceに文章を文字列で代入します。その中の大文字のみを内包表記で list化し、 cap_letters に代入して出力して下さい。’Hello World!’の場合は、[‘H’, ‘W’] を出力することになります。
大文字の判定は isupper() メソッドを使用します。 例えば c = ‘A’ の時は c.isupper() は True となり、c = ‘a’ の時は c.isupper() は False となります。
例題_4_3 内包表記 (forの二重化)
前章の例題_3_5(forの2重化)を内包表記で表現したいと思います。
以下は例題_3_5のコードで2次元listをフラット化(1次元化)するコードです。
ls = [[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23]]
ls_flat = []
for row in ls:
for x in row:
ls_flat.append(x)
ls_flat
[0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23]
内包表記を使うと中間の4行を1行で表記することができます。
ls = [[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23]]
ls_flat = [x for row in ls for x in row]
ls_flat
[0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23]
書き方は、x の後には最初に外側の for row in ls を記載し、スペースを置いて内側の for x in row をします。通常の表記法と同じ順番ですので覚えやすいかと思います。このlistのフラット化には内包表記が良く使われます。
練習問題_4_4 内包表記(forの二重化)
上記例題_4_3は1行ずつ行方向に展開するやり方でしたが、今度は列方向に取り出して
[0, 10, 20, 1, 11, 21, 2, 12, 22, 3, 13, 23]
となるリストを作成して下さい。練習問題_3_3(forの2重化の応用)の1番の解答例がヒントとなります。
例題_4_4 内包表記 (二次元のlist作成)
さて今度は以下のlsの二次元リストを内包表記で作成してみましょう。
汎用性を持たせるために、m行n列の行列を作成できるコードを書いてみます。
ls = [[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23]]
ls
[[0, 1, 2, 3], [10, 11, 12, 13], [20, 21, 22, 23]]
内包表記の場合は以下の様に記載します。
m, n = 3, 4
ls = [[10*i + j for j in range(n)] for i in range(m)]
ls
[[0, 1, 2, 3], [10, 11, 12, 13], [20, 21, 22, 23]]
i行 j列目の要素は 10*i + j で表されますので、それを for j in range(n)でn列分ループ処理したものを[]で括ってlist化します。更にそのlistをfor i in range(m)でm行分繰り返したものをまた[]で括ってlist化したものが二次元リストとなります。
練習問題_4_5 内包表記(転置行列)
上記例題_4_4ので作成した行列ををもとにそこから転置行列を作成するコードを内包表記を表して下さい。
例えば ls = [[0, 1, 2, 3], [10, 11, 12, 13], [20, 21, 22, 23]] の転置行列 trans_2d_list2(ls)は [[0, 10, 20], [1, 11, 21], [2, 12, 22], [3, 13, 23]] となります。
今回も、関数化する必要はありません。
練習問題_4_6 内包表記(行列の積)
前章、練習問題_3_8で作成した行列の積を内包表記で作成してみましょう。
A = [[10*i + j for j in range(4)] for i in range(3)]
B = [[10*i + j for j in range(4)] for i in range(4)]
で作成した行列AとBの積であるABを内包表記で作成して下さい。計算方法をを再度以下に示します。
積ABの0行0列目の要素は、Aの0行目とBの0列目のそれぞれの要素に対して初めから順番に掛け算した積の和を取ったものです。別の言い方ではAの0行目行ベクトルとBの0列目の烈ベクトルの内積とも言います。
積ABの i行 j列目の要素には、Aの i行目の行ベクトルとBの j列目の烈ベクトルの内積が入ります。従って、行列A(m行, k列) 行列B(k行, n列) の積ABは AB(m行, n列)となります。Aの列数(k)とBの行数(k)が同じ場合に掛け算が可能となります。 今回Aは3行4列、Bは4行4列となります。
関数化する必要はありません。ヒントは練習問題_3_4のヒントで示した2つの内積の計算方法のうち2つ目のやり方が内包表記に応用できます。
条件式
内包表記がfor文を1行で表すものなら、条件式は if ~ else 文を1行で表したものと言えます。
「条件 C がTrueの時は x Falseの時は y 」を表す式として「 x if C else y 」と書くことができます。
この記載方法をpythonでは 条件式 (Conditional Expression) と言います。(”三項演算子” と呼ばれることもあります)
例題_4_5 条件式
a には整数が与えられ、b には a が10未満の時は a それ以外は 10 となる数を代入します。
a = 3
b = a if a < 10 else 10
b
3
この様に1行でif文を表すことができます。a の値を変更して合っていることを確認してください。
条件式ではelseを省略することはできません。ここで「else 10」を記載しないで if のみの場合、SyntaxErrorが発生します。
練習問題_4_7 条件式(bool演算子)
例題_2_4、練習問題_2_9で作成したbool演算子の if ~ else文を条件式で表してみましょう。
- a と b には bool, int, float, strの何れかが代入される
- bool以外の int, float, strではそれぞれ 0, 0.0, ” がFalseで、それ以外はTrue
- a の not を表すnot_aには a が Trueの時 not_a にFalse、a が Falseの時 not_a にTrueを代入する
- a と b の and を表す and_ab には a または b のどちらかを代入され、bool(and_ab) が bool(a) and bool(b) と等しくなる様にする
- or を表す or_ab には a または b のどちらかを代入され、bool(or_ab) が bool(a) or bool(b) と等しくなる様にする
練習問題_4_8 条件式+内包表記
例題_4_2 は内包表記に if を追加したものですが、これをもう少し変えて、yが10より小さい時は yを、それ以外(else)の時は10を list化するという条件にしてみます。内包表記を使わない方法では以下のように表せます。
ly = []
for x in range(10):
if (x - 4)**2 < 10:
ly.append((x - 4)**2)
else:
ly.append(10)
ly
[10, 9, 4, 1, 0, 1, 4, 9, 10, 10]
これを条件式と内包表記を使用してリスト ly を作成してください。
まず、[] の中に条件式を記載して、その後に内包表記の for文を記載します。したがって elseがある場合は、ifの位置が例題_4_2とは全く異なります。
ジェネレータ
ここでは、ジェネレータ(generator) とジェネレータ関数(generator function) の2種類を取り上げます。
ジェネレータ(generator)
前記の例では、内包表記は list 内に記載しましたが、同じ内包表記を tuple と同じ丸括弧内に記載すると tuple ではなくジェネレータ(generator) という object になります。
l = [(i-1)**2 for i in range(4)]
g = ((i-1)**2 for i in range(4))
print(l, g)
type(l), type(g)
[1, 0, 1, 4] <generator object <genexpr> at 0x7f802000c510>
(list, generator)
list と generator の違いは、list では計算された要素全てがメモリ上に存在していますが、 generator は計算式として存在しており、各要素は for 文や next 関数で呼び出されたときにその都度一つずつ計算されて戻されます。従って要素を index による計算式で表せる場合は、list をよりも generator の方がメモリを節約できることになります。
generator は以下のように for 文、next 関数で要素を一つずつ呼び出すことができます。
まず for 文で要素を書き出してみます。
g = ((i-1)**2 for i in range(4))
for x in g:
print(x, end=', ')
1, 0, 1, 4,
また、next 関数でも要素を順番に呼び出すことができます。
g = ((i-1)**2 for i in range(4))
next(g), next(g), next(g), next(g)
(1, 0, 1, 4)
この場合に注意する点として、上記第一行の様に必ず変数に代入してインスタンスを作成する必要があります。next 関数でこのインスタンスを呼び出すことにより次の要素を入手することができます。この点が for 文と next 関数の違いとなります。
next 関数を while 文に使用する場合は以下の様になります。
最初の例は、next 関数の第二引数を使用しない場合です。この場合、データを出し切った後に next 関数を実行すると StopIteration が発動されますので、それを try ~ except で回避しています。
g = ((i-1)**2 for i in range(4))
while True:
try:
print(next(g), end=', ')
except StopIteration:
break
1, 0, 1, 4,
next 関数には第二引数としてデータを出し切った際の戻り値を指定することができます。以下はその戻り値を None に設定したときの例です。
g = ((i-1)**2 for i in range(4))
while True:
x = next(g, None)
if x != None:
print(x, end=', ')
else:
break
1, 0, 1, 4,
ジェネレータ関数(generator function)
def 文で定義する関数も呼び出した際にジェネレータの様に一つずつ要素を返すように作成することができます。この様な関数ジェネレータ関数(generator function)またはジェネレータ(generator)と言います。
通常の関数は return で戻り値を返しますが、ジェネレータ関数では return の代わりに yield を使用します。以下の例はシーケンスの最後の要素から最初の要素に向かって順番に表示するジェネレータ関数 reverse(data) です。
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
yield は上記の様に for文などの繰り返しループの中で使用します。
以下はジェネレータを for 文で使用した場合です。
for char in reverse('golf'):
print(char, end=', ')
f, l, o, g,
前節と同様にジェネレータ関数を next 関数で呼び出す場合は、インスタンスを作成してから next 関数に適用します。
g = reverse('golf')
next(g), next(g), next(g), next(g)
(‘f’, ‘l’, ‘o’, ‘g’)
以下は間違った使用法ですが、next 関数で直接ジェネレータを呼び出すと最初の文字が繰り返されるだけとなります。
next(reverse('golf')), next(reverse('golf')), next(reverse('golf')), next(reverse('golf'))
(‘f’, ‘f’, ‘f’, ‘f’)
練習問題_4_9 ジェネレータ関数 フィボナッチ数
それではここで幾つかジェネレータを作成してみましょう。
最初の問題はフィボナッチ数列 fibo_g(n, size = 10) です。引数 n は自然数、戻り値は (x, n) の二値で x は n 番目のフィボナッチ数です。呼び出す毎にn, n+1, n+2, … , n+size-1 番目のフィボナッチ数が戻される様に作成してください。ちなみにフィボナッチ数列x(n)は、1(1), 1(2), 2(3), 3(4), 5(5), 8(6), … と数えることにします。
簡易版で作成してください。ヒントの代わりとして以前作成したフィボナッチ関数を参考にしてください。
練習問題_4_10 ジェネレータ関数 素数
次のジェネレータ関数は素数 prime_num_g(n, size = 10) です。引数 n は自然数、戻り値は n 以上の素数で、呼び出す毎に小さい順に素数が戻される様に作成してください。size の個数を戻したところで Iteration を終了します。
簡易版で作成してください。これもヒントは以前作成した素数の関数を参考にしてください。