Get started learning Python with DataCamp's free Intro to Python tutorial. Learn Data Science by completing interactive coding challenges and watching videos by expert instructors. Start Now!
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join 11 million other learners and get started learning Python for data science today!
Good news! You can save 25% off your Datacamp annual subscription with the code LEARNPYTHON23ALE25 - Click here to redeem your discount
マップ、フィルター、リデュース
Map、Filter、および Reduce は、関数型プログラミングのパラダイムです。これらはプログラマー(あなた)がループや分岐の複雑さに煩わされることなく、よりシンプルで短いコードを書けるようにします。
基本的に、これらの3つの関数は、あなたが一度に複数のイテラブルに関数を適用することを可能にします。map
とfilter
はPythonに組み込まれており(__builtins__
モジュールに存在します)、インポートは必要ありません。しかし、reduce
はfunctools
モジュールにあるためインポートが必要です。では、これらの機能がどのように動作するかを理解していきましょう。まずはmap
から始めます。
Map
Pythonにおけるmap()
関数の構文は以下の通りです:
map(func, *iterables)
ここでfunc
は、各要素が適用される関数であり、iterables
(いくつでも可)の要素です。アスタリスク(*
)はイテラブルがいくつでも可能であることを意味し、func
が必要な入力引数としてその正確な数を持っている限りです。例を見る前に、次の点に注意してください:
- Python 2では、
map()
関数はリストを返します。しかし、Python 3では、map()
関数はmap object
というジェネレータオブジェクトを返します。結果をリストとして取得するには、組み込みのlist()
関数をmap object
に対して呼び出すことができます。つまり、list(map(func, *iterables))
func
の引数の数は、リストしたiterables
の数でなければなりません。
これらのルールがどのように作用するか、以下の例で見てみましょう。
私のお気に入りのペットの名前のリスト(iterable
)が、すべて小文字であり、大文字に変換する必要があるとしましょう。従来のPythonコードでは、次のように書くでしょう:
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = []
for pet in my_pets:
pet_ = pet.upper()
uppered_pets.append(pet_)
print(uppered_pets)
これは['ALFRED', 'TABITHA', 'WILLIAM', 'ARLA']
を出力します。
map()
関数を使用すれば、これがより簡単で柔軟性も増します。次のようにします:
# Python 3
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = list(map(str.upper, my_pets))
print(uppered_pets)
これにより、同じ結果が出力されます。このケースでのfunc
はstr.upper
であり、iterables
はmy_pets
リストです ― ちょうど1つのイテラブルです。また、str.upper
関数を呼び出していないことに注意してください(str.upper()
とすることはしません)、map
関数がmy_pets
リストの_各要素に対して_これを行います。
重要なことは、str.upper
関数は定義上1つの引数だけを必要とするため、1つのイテラブルだけを渡します。きちんと理解するために別の例で説明します。
たとえば、どこかで計算した円の面積のリストがあり、すべて小数点以下5桁で、それらをそれぞれの位置の小数桁に丸める必要があるとします。つまり、リストの最初の要素を小数点以下1桁に、2番目の要素を小数点以下2桁に、3番目の要素を小数点以下3桁に丸める必要があります。map()
を使えば簡単です。
Pythonではすでに、丸めたい数値と丸めたい小数桁数の2つの引数を取る組み込みのround()
関数があります。関数が2つの引数を必要とするので、2つのイテラブルを渡す必要があります。
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 7)))
print(result)
map()
の美しさが見えますか?この柔軟性を想像できますか?
range(1, 7)
はround
関数の2番目の引数として機能します(各反復の必要な小数桁数)。したがって、map
がcircle_areas
を反復する際に、最初の反復でcircle_areas
の最初の要素3.56773
がrange(1,7)
の最初の要素1
とともにround
に渡され、round(3.56773, 1)
が形成されます。2番目の反復では、circle_areas
の2番目の要素5.57668
がrange(1,7)
の2番目の要素2
とともにround
に渡され、round(5.57668, 2)
となります。この過程はcircle_areas
リストの終わりまで続きます。
おそらく疑問に思っているでしょう: 「最初のイテラブルの長さより少ないまたは多いイテラブルを渡した場合はどうなるんだろう?つまり、上記の関数で2番目のイテラブルとしてrange(1, 3)
やrange(1, 9999)
を渡した場合にはどうなるのか」と。そして答えは単純です: 何も起こりません!いいえ、それは真実ではありません。「何も」起こらないというのは、map()
関数が例外を発生させることはなく、ただ要素を反復し、関数の2番目の引数が見つからなくなると処理を止めて結果を返すという意味です。
したがって、たとえばresult = list(map(round, circle_areas, range(1, 3)))
を評価しても、circle_areas
とrange(1, 3)
の長さが異なっていてもエラーは発生しません。代わりに、Pythonは次のように動作します: circle_areas
の最初の要素とrange(1,3)
の最初の要素を取り、それをround
に渡します。round
はそれを評価し、結果を保存します。その後、2番目の反復、circle_areas
の2番目の要素とrange(1,3)
の2番目の要素round
が再度保存します。3番目の反復(circle_areas
にはサード要素があります)には、circle_areas
の3番目の要素を取り、range(1,3)
の3番目の要素を取ろうとするが、range(1,3)
に3番目の要素がないため、Pythonはその結果を出力して処理を停止します。場合によっては、 [3.6, 5.58]
になります。
試してみてください。
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 3)))
print(result)
同様に、もしcircle_areas
が2番目のイテラブルより少なくても、Pythonは単に次の要素が見つからない場合に停止します。
map()
関数の知識を深めるために、これを使って独自のzip()
関数を実装しましょう。zip()
は複数のイテラブルを受け取り、それぞれの要素を含むタプルを作成する関数です。Python 3では、map()
のように、ジェネレータオブジェクトが返され、組み込みのlist
関数を呼び出すことで簡単にリストに変換できます。zip()
の理解を深めるため、以下のインタープリタセッションを使用しましょう。
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(zip(my_strings, my_numbers))
print(results)
おまけで、上記のセッションでmy_strings
とmy_numbers
が同じ長さでない場合に何が起きるか予想できますか?できない?試してみてください。一方の長さを変えてみてください。
それでは独自のzip()
関数に進みましょう!
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(map(lambda x, y: (x, y), my_strings, my_numbers))
print(results)
見てください!zip
と同じ結果が得られました。
def my_function()
の標準的な方法を使わずに関数を作成する必要もありませんでした。このように、map()
や一般的にPythonが非常に柔軟であることがわかります!単にlambda
関数を使いました。これは、標準の関数定義方法(def function_name()
)を使用してはならないというわけではなく、今でも可能です。単にコードを少なく(「Pythonic」に)書きたかっただけです。
これがmap関数についてです。それではfilter()
へ進みましょう。
Filter
filter()
は、各要素を関数に通過させ、その結果を返すmap()
とは異なり、まず関数がブール値(真または偽)を返すことを要求し、次にイテラブルの各要素を関数に通過させ、偽の要素を「フィルタリング」(除去)します。以下の構文があります:
filter(func, iterable)
filter()
について注意すべき以下のポイントがあります:
map()
とは異なり、1つのイテラブルのみが必要です。func
引数はブール型を返す必要があります。そうしない場合、filter
は渡されたiterable
をそのまま返します。また、1つのイテラブルのみが必要であるため、func
は必然的に1つの引数のみを取る必要があります。filter
はfunc
に渡された各要素を評価し、真と評価されるものだけを返します。その名の通り ― 「フィルター」です。
いくつか例を見てみましょう。
以下は化学の試験の10人の学生のスコアのリスト(iterable
)です。75点以上の合格者をfilter
を使ってフィルタリングします。
# Python 3
scores = [66, 90, 68, 59, 76, 60, 88, 74, 81, 65]
def is_A_student(score):
return score > 75
over_75 = list(filter(is_A_student, scores))
print(over_75)
次の例は回文検出器です。「回文」とは、前から読んでも後ろから読んでも同じになる言葉やフレーズ、または文字列のことです。回文である言葉をタプル(iterable
)からフィルタリングします。
# Python 3
dromes = ("demigod", "rewire", "madam", "freer", "anutforajaroftuna", "kiosk")
palindromes = list(filter(lambda word: word == word[::-1], dromes))
print(palindromes)
これは['madam', 'anutforajaroftuna']
を出力します。
素晴らしいですね?最後にreduce()
です。
Reduce
reduce
は、イテラブルの要素に渡された関数に2つの引数を適用し、オプションで初期値も指定できます。以下の構文があります:
reduce(func, iterable[, initial])
ここでfunc
は、イテラブルの各要素が累積的に適用される関数で、initial
は計算の前にイテラブルの要素の前に置かれ、イテラブルが空のときにデフォルトとして機能するオプションの値です。reduce()
について次の点に注意してください:
1. func
は2つの引数を必要とし、最初の引数はイテラブル内の最初の要素(initial
が指定されていない場合)と2番目の要素です。initial
が指定されている場合、それはfunc
の最初の引数となり、イテラブルの最初の要素が2番目になります。
2. reduce
はiterable
を単一の値に「減らします」。
例を見てみましょう。
Pythonの組み込みsum()
関数の独自バージョンを作成してみましょう。sum()
関数は、渡されたイテラブル内のすべての項目の合計を返します。
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers)
print(result)
結果は予期通り68
です。
では何が起こったのでしょう?
いつものように、それはすべての反復に関することです:reduce
はnumbers
内の最初と2番目の要素を取り、それぞれcustom_sum
に渡します。custom_sum
はそれらの合計を計算し、それをreduce
に返します。その結果をreduce
はcustom_sum
の最初の要素として適用し、次の要素(3番目)をcustom_sum
に2番目の要素として取ります。これを繰り返し(累積的に)、numbers
が尽きるまで行います。
オプションのinitial
値を使用した場合に何が起きるか見てみましょう。
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers, 10)
print(result)
結果は予測通り78
になります。それはreduce
が最初に10
をcustom_sum
の最初の引数として使用するためです。
これでPythonのMap、Reduce、及びFilterの全容です。各関数の理解を深めるため、以下の練習問題に挑戦してください。
Exercise
この演習では、map
, filter
, reduce
を使用して壊れたコードを修正します。
from functools import reduce
# Use map to print the square of each numbers rounded
# to three decimal places
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
# Use filter to print only the names that are less than
# or equal to seven letters
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
# Use reduce to print the product of these numbers
my_numbers = [4, 6, 9, 23, 5]
# Fix all three respectively.
map_result = list(map(lambda x: x, my_floats))
filter_result = list(filter(lambda name: name, my_names, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers, 0)
print(map_result)
print(filter_result)
print(reduce_result)
#### Map
from functools import reduce
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
my_numbers = [4, 6, 9, 23, 5]
map_result = list(map(lambda x: round(x ** 2, 3), my_floats))
filter_result = list(filter(lambda name: len(name) <= 7, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers)
print(map_result)
print(filter_result)
print(reduce_result)
test_output_contains("[18.922, 37.088, 10.562, 95.453, 4.666, 78.854, 21.068]")
test_output_contains("['olumide', 'josiah', 'omoseun']")
test_output_contains("24840")
success_msg("Congrats! Nice work.")
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join over a million other learners and get started learning Python for data science today!