- 機(jī)械工程師Python編程:入門、實(shí)戰(zhàn)與進(jìn)階
- (西)安琪兒·索拉·奧爾巴塞塔
- 2322字
- 2024-09-10 16:13:08
2.1.6 filter、map和reduce函數(shù)
在函數(shù)式編程中,我們從不修改集合中的元素,而是創(chuàng)建一個(gè)新的集合來(lái)反映對(duì)該集合的操作的更改。有三個(gè)操作構(gòu)成了函數(shù)式編程的基石,而且可以實(shí)現(xiàn)對(duì)集合的、我們能想到的任何修改:filter、map和reduce。
1. filter函數(shù)
filter函數(shù)接收一個(gè)集合,過濾掉某些元素并生成一個(gè)新集合。元素的過濾是根據(jù)判定函數(shù)進(jìn)行,判定函數(shù)會(huì)接受參數(shù),根據(jù)該參數(shù)是否通過給定的測(cè)試來(lái)返回True或False。
圖2-1說明了過濾器的操作。

圖2-1 過濾一個(gè)元素集
圖2-1顯示了由A、B、C和D四個(gè)元素組成的元素集。元素集下面是一個(gè)代表判定函數(shù)的框,它決定哪些元素被保留,哪些元素被丟棄。元素集中的每個(gè)元素都被傳遞給該函數(shù),只有通過測(cè)試的元素才會(huì)出現(xiàn)在結(jié)果集合中。
Python有兩種方法可以過濾集合:一,使用全局函數(shù)filter;二,如果集合是一個(gè)列表,使用列表推導(dǎo)式。這里我們主要關(guān)注filter函數(shù),下一節(jié)會(huì)介紹列表推導(dǎo)式。filter函數(shù)的輸入?yún)?shù)是一個(gè)函數(shù)(判定)和一個(gè)集合:

讓我們寫一個(gè)lambda判定函數(shù)來(lái)測(cè)試一個(gè)數(shù)字是否為偶數(shù):

現(xiàn)在讓我們使用lambda函數(shù)來(lái)過濾一個(gè)數(shù)字列表,并獲得一個(gè)只有偶數(shù)的新集合:

需要注意的是,filter函數(shù)并不會(huì)返回列表,而是返回迭代器。迭代器允許對(duì)一組元素進(jìn)行依次迭代。如果你想了解更多關(guān)于Python迭代器及其底層原理,請(qǐng)參閱https://docs.python.org/3/library/stdtypes.html#typeiter和https://docs.python.org/3/glossary.html#termiterator上的文檔。
我們可以使用前面看到的list函數(shù)使用所有迭代器的值,并將它們放入一個(gè)列表中,也可以使用for循環(huán)來(lái)使用迭代器:

2. map函數(shù)
map函數(shù)對(duì)原集合中的每個(gè)元素進(jìn)行函數(shù)運(yùn)算,并將結(jié)果存儲(chǔ)到一個(gè)新的元素集中。兩個(gè)元素集的大小相同。
圖2-2描繪了映射操作。

圖2-2 映射一個(gè)元素集
我們通過一個(gè)映射函數(shù),對(duì)元素A、B、C和D組成的源集合進(jìn)行運(yùn)算,如圖2-2中的五邊形所示,運(yùn)算的結(jié)果存儲(chǔ)在一個(gè)新的元素集中。
我們可以使用全局函數(shù)map來(lái)映射一個(gè)元素集,對(duì)于列表,還可以使用列表推導(dǎo)式。我們稍后將討論列表推導(dǎo)式,現(xiàn)在,讓我們研究如何使用map函數(shù)來(lái)映射元素集。
全局函數(shù)map接收兩個(gè)參數(shù),即一個(gè)映射函數(shù)和一個(gè)源集合:

以下代碼將一個(gè)名稱列表與它們的長(zhǎng)度進(jìn)行映射:

與filter函數(shù)一樣,map返回一個(gè)迭代器,可以使用list函數(shù)生成列表。在上面的示例中,結(jié)果列表包含了名稱列表中每個(gè)名稱的字符數(shù):Angel對(duì)應(yīng)5個(gè)字符,Alvaro對(duì)應(yīng)6個(gè)字符,以此類推。這樣就把每個(gè)名稱映射成了表示其長(zhǎng)度的數(shù)字。
3. reduce函數(shù)
reduce函數(shù)是三個(gè)函數(shù)中最復(fù)雜,但同時(shí)用途最廣泛的。它可以創(chuàng)建一個(gè)少于、多于或等于原集合的元素?cái)?shù)量的元素集。為構(gòu)造這個(gè)新的元素集,它首先對(duì)第一和第二個(gè)元素應(yīng)用reduce函數(shù);然后,對(duì)第三個(gè)元素和第一次操作的結(jié)果再次應(yīng)用reduce函數(shù);接著,對(duì)第四個(gè)元素和第二次操作的結(jié)果再次應(yīng)用reduce函數(shù)。這樣一來(lái),結(jié)果就會(huì)累積起來(lái)。一個(gè)圖在這里會(huì)有所幫助。請(qǐng)看圖2-3。
本例中的reduce函數(shù)將元素集中的每個(gè)元素(A、B、C和D)累積為單個(gè)元素:ABCD。

圖2-3 將reduce函數(shù)用于一個(gè)元素集
reduce函數(shù)接收兩個(gè)參數(shù),即累積結(jié)果和元素集中的一個(gè)元素:

該函數(shù)在處理完新元素后返回累積結(jié)果。
Python沒有提供全局函數(shù)reduce,但是有一個(gè)叫functools的包,里面有一些處理高階函數(shù)的有用操作,包括reduce函數(shù)。這個(gè)函數(shù)不返回迭代器,而是直接返回生成的元素集或元素。函數(shù)的語(yǔ)法如下:

讓我們看一個(gè)例子:

在這個(gè)示例中,reduce函數(shù)返回元素“ABCD”,即元素集中的所有字母連接起來(lái)的結(jié)果。reduce過程開始時(shí),先接收前兩個(gè)字母A和B,并將它們連接成AB。對(duì)于第一步而言,Python使用集合的第一個(gè)元素(A)作為累積結(jié)果,并將它和第二個(gè)元素上應(yīng)用reduce函數(shù)。然后,它移動(dòng)到第三個(gè)字母C,并將其與當(dāng)前的累積結(jié)果AB連接起來(lái),從而生成新的結(jié)果:ABC。最后一步,對(duì)D字母同樣操作,生成最終結(jié)果ABCD。
當(dāng)累積結(jié)果和元素集的元素類型不同時(shí)會(huì)發(fā)生什么?在這種情況下,我們不能將第一個(gè)元素作為累積結(jié)果,因此reduce函數(shù)需要我們提供第三個(gè)參數(shù)作為初始累積結(jié)果:

例如,假設(shè)我們想將前面用到的名稱集合進(jìn)行縮減,以獲得這些名稱長(zhǎng)度的總和。在這種情況下,累積結(jié)果是數(shù)字,而集合中的元素是字符串,我們不能使用第一項(xiàng)作為累積的長(zhǎng)度。如果我們忘記給reduce函數(shù)提供初始結(jié)果,Python會(huì)彈出一個(gè)錯(cuò)誤來(lái)提醒我們:

這種情況,我們應(yīng)該傳遞0作為初始累積長(zhǎng)度:

一個(gè)有趣的點(diǎn)在于,如果累積結(jié)果和集合元素的類型不同,我們總可以用map函數(shù)連接reduce函數(shù)以獲得相同的結(jié)果。例如,在前面的練習(xí)中,我們也可以這么做:

在這段代碼中,我們首先將列表names映射到一個(gè)名稱長(zhǎng)度的列表lengths中。然后,我們縮減列表lengths來(lái)求和所有的值,而不需要提供起始值。
當(dāng)使用一個(gè)常規(guī)操作來(lái)縮減元素——如兩個(gè)數(shù)字求和或兩個(gè)字符串的連接——我們不需要編寫lambda函數(shù);可以直接將現(xiàn)有的Python函數(shù)傳遞給reduce函數(shù)。例如,在縮減數(shù)字時(shí),Python提供了一個(gè)有用的模塊,名為operator.py。這個(gè)模塊定義了對(duì)數(shù)字進(jìn)行操作的函數(shù)。使用這個(gè)模塊,我們可以簡(jiǎn)化之前的代碼如下:

這個(gè)代碼更短,更易讀,因此我們?cè)诒緯袝?huì)傾向于使用這種形式。
operator.add函數(shù)由Python定義如下:

如你所見,這個(gè)函數(shù)等價(jià)于我們之前定義的兩個(gè)數(shù)字求和的lambda函數(shù)。后面我們會(huì)看到更多由Python定義的,可以和reduce函數(shù)一起使用的函數(shù)。
到目前為止,我們所有的示例都將元素集合縮減到一個(gè)值,但reduce函數(shù)可以做得更多。事實(shí)上,filter和map函數(shù)都是reduce函數(shù)的特例。我們可以使用reduce函數(shù)來(lái)過濾和映射一個(gè)元素集。但我們并不會(huì)在這里停下來(lái)分析它;如果你有興趣,可以試著自己弄清楚。
讓我們看一個(gè)例子,我們希望基于列表names創(chuàng)建一個(gè)新的集合,其中的每個(gè)元素都是前面所有的名稱與當(dāng)前名稱的組合,由連接符(-)進(jìn)行分隔。結(jié)果類似如下:

我們可以使用以下代碼來(lái)做到這一點(diǎn):

這里,我們使用compute_next_name來(lái)確定列表中的下一個(gè)項(xiàng)。reduce函數(shù)內(nèi)部的lambda函數(shù)將累積結(jié)果連接起來(lái),生成由組合名稱形成的列表,和一個(gè)由新元素組成的新列表。需要提供空列表作為初始結(jié)果,因?yàn)榱斜碇械拿總€(gè)元素的類型(字符串)與結(jié)果的類型(字符串組成的列表)不同。
如你所見,reduce函數(shù)用途非常廣泛。
- Advanced Machine Learning with Python
- Learning Java Functional Programming
- LaTeX Cookbook
- Cocos2d-x游戲開發(fā):手把手教你Lua語(yǔ)言的編程方法
- Visual Basic編程:從基礎(chǔ)到實(shí)踐(第2版)
- React Native Cookbook
- Learning Docker Networking
- C++ Application Development with Code:Blocks
- Learning Grunt
- Kotlin進(jìn)階實(shí)戰(zhàn)
- 零基礎(chǔ)學(xué)Java第2版
- Python應(yīng)用與實(shí)戰(zhàn)
- Getting Started with hapi.js
- Mastering PostgreSQL 11(Second Edition)
- R語(yǔ)言編程基礎(chǔ)