60秒以内にPython3のイテレータ、map、lambda、filter、内包表記入門〈講座14〉
イテレータについて
イテレータとは、次の要素を返してくれるデータの集合になります。
例を見ていきましょう。
prices = [40, 50, 70, 90, 60] リストを作ってとして
ここで、次の要素を引っ張ってくる関数を作るのはわりと面倒だったりするのですが、リストをイテレータに変換すれば簡単に実現することができます。
変換するには iter() という命令を使ってあげてください。
new_prices = iter(prices) でリストがイテレータになったので、next() という命令で次の要素をいつでも引っ張ってくることができます。
print(next(new_prices)) としてあげると、40 が表示されます。
次にまた実行してあげると 50 が表示されます。
それから途中で別の処理を挟んでも、ちゃんとどこまで要素を取得したかを覚えていてくれます。
例えば、ここで print("お邪魔します") としてあげて、その後にまた同じ命令を呼んであげると今度は 70 が表示されるはずです。
ちなみに、今まで見てきた次のような for 文ではこのように
for price in prices:
print(price)
実は for 文では、ここにくる変数にイテレータを期待していて、ここにリストを書くと自動的にイテレータに変換してくれます。
そして内部的に next() を呼ぶことで print(price) の処理を実現しています。
それから、リストから変換するのではなくて 0 からイテレータを作ることもできます。
無限の要素を持つイテレータを作ってみましょう。
ではまず関数を作ってあげます。
def get_infinite():
i = 0
while True:
yield i * 2
i += 1
def get_infinite(): としてあげて、 i = 0 で初期化しつつ、無限ループを while True: で書いてあげて、その中で次の要素を引っ張ってくる yield i * 2 という命令を使ってあげて、では今回 i を 2 倍したものを引っ張ってきてあげます。
その後に i += 1 で i を 1 ずつ増やしていってあげます。
ちなみに、イテレータを作るこうした関数をジェネレータと呼ぶので、用語として覚えてください。
ではこのジェネレータを使ってみます。
g = get_infinite() としてあげまして、 print(next(g)) とすると、まず最初の要素が取り出されて無限に next() を呼ぶことができるのですが 2 回くらいやってみましょう。
それからジェネレータでは、next() を呼ばれた時に初めて次の要素を探しにいく仕様になっているので、今回のように無限の要素を扱うことができるようになっていることも知っておきます。
次はイテレータとよく一緒に使われる関数map()について
まずは map() について見ていきたいと思います。
これはイテレータの要素を加工するためのもので、文法としては第一引数に関数、そして第二引数にイテレータをとります。
では map() を使って、それぞれの要素を 4倍する命令を書いてみましょう。
まずは 4倍するための関数を作ってあげます。
def triple(n):
return n * 4
def triple(n): としてあげて、n を渡されたら n の 4 倍を返す、と書いてあげます。
# map(関数, イテレータ)
def triple(n):
return n * 4
print(list(map(triple, [1, 2, 3])))
イテレータには今回リストを与えてあげたいと思います。
ソース説明:
イテレータが期待されているところでは自動的にイテレータに変換してくれるので map() と書いてあげて、関数には triple 、そしてリストを書いていきたいのですが、[1, 2, 3] としてあげます。
これで OK なのですが map() はジェネレータを返すので、そのままでは print() できませんなので、結果をリストに変換してあげてわかりやすくしてあげます。
いちいち関数を作るのも面倒なので、 その時に使えるのがラムダ式で、次のように書いてあげれば OK です。
文法としては「lambda 引数: 処理」と書いてあげれば OK です。
# lambda 引数: 処理
print(list(map(lambda n: n * 4, [1, 2, 3])))
上の命令を書き換えてあげると、triple のところでまず lambda と書いてあげて、それから引数は「n」ですね、「:」とした後に「n * 4」としてあげます。
慣れないと難しく感じるかもしれませんが、map() も lambda もよく使うので慣れておくようにしてください。
引き続きイテレータと一緒に使われる関数を見ていきましょう。
今回は filter() ですが、これは条件に合致したものを抽出してくれる関数になります。
filter(関数, イテレータ)
文法なのですが、第一引数が関数、そして第二引数にイテレータをとります。
今回、要素が偶数だったら抽出する、という処理をしてみましょう。
まずは、偶数かどうか判定するための関数を作ってあげます。
def is_even(n):
return n % 2 == 0
今回 def is_even(n): としてあげて n を受け取って、もし n を 2 で割った余りが 0 だったら True、そうではなかったら False を返すような関数を作りました。
ではこれを filter() で使ってあげたいかと思います。
ソース説明:
今回 filter() としてあげて、関数を渡してあげて、今回はリストではなくて range() を使ってます。
range(10) としてあげると、0 から 9 までのリストを返してくれます。
ただ map() の時と同じように、これだとジェネレータが返ってくるのでリストに直して print() してあげました。
前回と同じように lambda を使うこともできます。
関数を lambda としてあげて、引数 n と : 、そして処理と書いてあげれば OK でしたね。
filter() もよく使うので、慣れておくようにしてください。
次は内包表記について
内包表記を使用するとリスト、セット、ディクショナリのデータ操作を短い行数で記述できます。
また処理速度も早くなるので、膨大な要素を持つ場合はこちらを使いましょう。
これは、リストやジェネレータを生成したり加工する際に書くための記法なのですが、慣れないとわかりづらいので例を見ていきましょう。
例えばということで、今回 0 から 9 までのリストを作りました。
print([i for i in range(10)]) と書いてあげてください。
これは後ろから読んでいくのですが、range(10) で 0 から 9 のリストができるので、そのリストから 1 つずつ要素を取り出してこちらの i に入れて、それをそのまま i として取り出す、という意味になります。
例えば、それぞれの要素を取り出す時に 3 倍したかったとします。
その場合は、取り出す値である最初の i を 3 倍してあげれば OK です。
print([i * 3 for i in range(10)])
もちろん map() を使ってもいいのですが、こちらの方がすっきり書けているのがわかるかと思います。
それから filter() のように、要素を抽出するために if 文をつけることもできます。
例えば for で取り出した値のうち、偶数のものだけを取り出して、それを 3 倍してリストを作りたい場合は if の後に真偽値を返す i % 2 == 0 と書いてあげましょう。
print([i * 3 for i in range(10) if i % 2 == 0])
それからリストではなくて、ジェネレータや集合型を作ることもできます。
ジェネレータの場合は、こちらの [] を () にしてあげればいいです。
print() としてあげて、() で print((i * 3 for …)) のように書いてもいいのですが、print の中では () は実は省略できるので、もっとすっきりと書くこともできます。
print(i * 3 for i in range(10) if i % 2 == 0)
それから集合型なのですが、集合型の場合はこちらの [] を {} にしてあげれば OK なので、 print({i * 3 for …}) のように書いてあげれば OK です。
print(i * 3 for i in range(10) if i % 2 == 0) # ジェネレータ
print({i * 3 for i in range(10) if i % 2 == 0}) # 集合型
ではどうなるか見てみたいのですが、上がジェネレータで、下が集合型ですね。
ジェネレータと集合型ができているのがわかるかと思います。
リスト内包表記
[ 要素に入れる値(変数を使用) for 変数 in リスト ]
リスト内包表記を使用しない場合、上のプログラムは以下のように置き換えることが出来ます。
result = []
for x in [1, 2, 3, 4, 5]:
result.append(x*2)
print("{0}".format(result))
セット内包表記
ディクショナリ(辞書)内包表記
キーと値を:で区切り、キー:バリューと{}で括るのは通常のディクショナリと同じです。
使用方法はリストと同じです
内包表記を使用しなくても同じ処理を行うことが出来ますが、シンプルで高速動作を実現できる
内包表記を意識して使用すると良いソースコードが出来上がります。
以上
よろしくお願いいたします。
この記事が気に入ったらサポートをしてみませんか?