高階関数とは?
高階関数とは関数を引数として受け取ったり、関数を戻り値とするような関数のことです。
高階関数のメリットを事例を元に紹介します。
①文字列のリストの全ての要素にupper関数を適用し、全て大文字にした文字列のリストが欲しい場合。
1 2 3 4 | fruits = [ 'apple' , 'banana' , 'orange' ] fruits_len = [] for f in fruits: fruits_len.append( len (f)) |
②次は整数のリストの各要素を絶対値にしたリストが欲しい場合。
1 2 3 4 | integers = [ 1 , - 2 , 3 , - 4 , - 5 ] abs_result = [] for i in integers: abs_result.append( abs (i)) |
①, ②は一見無関係に見えますが、”リストの全要素に関数を適用したリストを返す”という制御フローパターンは同一です。
その制御フローをfor文で実現していますが、map関数を使って”リストの全要素に関数を適用したリストを返す”制御フローを実現できます。
①をmap関数を使って書き換える
1 2 | fruits = [ 'apple' , 'banana' , 'orange' ] fruits_len = list ( map ( len , fruits)) |
②をmap関数を使って書き換える
1 2 | integers = [ 1 , - 2 , 3 , - 4 , - 5 ] abs_result = list ( map ( abs , integers)) |
①,②のコードはそれぞれ4行ありますが、map関数を利用するとなんと2行で書くことができます。
map関数についてより詳しく解説します。
map(function, iterable, …)
map関数は、イテラブル(iterable)の全ての要素に関数(function)を適用するイテレータを返します。
複数のiterableが渡されたら、functionはその個数と同じ引数を取る必要があります。全てのiterableから値を並行して取得し、functionを適用するイテレータを返します。
map関数はPythonのビルトイン関数です。
1 2 3 4 5 6 7 8 | nums = [ - 1 , 2 , - 3 ] nums2 = [ 4 , 1 ] abs_result = list ( map ( abs , nums)) # (1) min_result = list ( map ( min , nums ,nums2)) # (2) print (abs_result) print (min_result) |
【結果】
1 2 | [1, 2, 3] [-1, 1] |
(1)のmap関数は、与えられた数値のリストを絶対値にするイテレータを返しています。そして、list関数でイテレータをリスト化しています。
map(abs, nums)は次のシーケンスのイテレータを返します。
abs(-1), abs(2), abs(-3)
(2)のmap関数は、2つの与えられた数値のリストの各要素を比較し、小さい方にするイテレータを返します。
map(min, nums ,nums2)は次のシーケンスのイテレータを返します。
min(-1,4), min(2,1)
numsの要素数は3つ、nums2の要素数は2つなので、nums2の要素数を全て取り出したらイテレータは終了します。
このように複数のイテラブルが与えられた場合最短のイテラブルが尽きた時点で終了します。
高階関数の認知度
実際はmap関数やfilter関数はビルトインとしてあるくらい便利なのですが活用しているプログラマーは少ないです。
多くの人は高階関数で端的に表現できるシーケンス制御を何度も何度もfor文とif文で書いています。
groupbyなどのmap,filter,reduceの派生系を知っているプログラマーはほとんどいません。しかし、知れば知るほど奥が深く非常に便利です。
内包表記とmap
mapやfilterはリスト内包表記で代用できます。
内包表記とmapの特徴を見てみましょう。
1 2 3 4 5 6 7 | def power2(x): return x * x nums = [ 1 , 2 , 3 , 4 , 5 ] r = list ( map (power2, nums)) # (1) r1 = [power2(n) for n in nums] # (2) r2 = [n * n for n in nums] # (3) |
(1)、(2)、(3)はどれも[1, 4, 9, 16, 25]のリストを生成しますが表現が異なります。
(1) list(map(power2, nums))
for文がなく分かりやすいです。イテレーターでも問題ない場合はリスト化する必要はありません。メモリの節約になるため積極的にイテレーターを使うといいと思います。
変数がなく、一番無駄がない書き方です。
1 | r = map (power2, nums) # rをイテレーターとして使う |
mapした結果をすぐにリスト化するのであれば(2)の方がスッキリしていてオススメです。
(2) [power2(n) for n in nums]
Pythonicな書き方です。if文と組み合わせることもでき使い勝手が良いです。個人的には②のスタイルが好みです。
(3) [n*n for n in nums]
②の変形パターンです。抽出した要素に適用する処理をリスト内に直接各方法です。単純な式ならこの表現もありだと思います。
コメントを残す