Clojure 學習筆記 :8 遍歷元素

Clojure 零基礎 學習筆記 遍歷 map filter reduce 匿名函數(shù)


體驗聲明式[1]的 “自動化” 遍歷

遍歷是一個非常常見的需求刚陡,我們經(jīng)常需要把一個集合中的每一個元素都取出來搗鼓點什么。這次我們來介紹一下函數(shù)式世界里非常實用的幾個函數(shù),它們能非常方便地處理這些遍歷問題:

  1. 依次把集合中的每一個元素取出來執(zhí)行某種操作。
  2. 找出集合中所有滿足某一指定條件的元素。
  3. 依次取出元素窥突,進行一系列操作后,把這次操作返回值和下一個元素一起硫嘶,再次進行操作...直至最后一個元素阻问。

解決第一個問題的函數(shù)是我們已經(jīng)見過的 map 函數(shù),它能把集合中的元素依次取出作為指定函數(shù)的參數(shù)沦疾,并把每次執(zhí)行的返回值以列表形式返回称近。
第二個“篩選”問題,我們使用 filter 函數(shù)來解決哮塞,我們會在接下來的內(nèi)容中來一起認識一下這個新伙伴刨秆。
前兩個問題比較容易理解,第三個問題看起來比較麻煩忆畅, reduce 函數(shù)可以用來處理這種問題衡未,這個過程稱為“規(guī)約”,我們馬上就會了解到如何來使用它家凯。


首先我們來和們的老朋友 map 函數(shù)打個招呼缓醋。我們來看看如何使用它來把一個數(shù)字集合中所有的數(shù)字都加上 1:

=> (defn plus-one
      [x]
      (+ x 1))
#'user/plus-one
=> (map plus-one [41 443 24346 23 54 3 35])
(42 444 24347 24 55 4 36)
; 事實上 Clojure 已經(jīng)內(nèi)置了函數(shù) inc,
; 它與我們自己實現(xiàn)的 plus-one 函數(shù)在功能上一模一樣
=> (map inc [41 443 24346 23 54 3 35])
(42 444 24347 24 55 4 36)

非常的簡便绊诲,如果你想進行其他操作送粱,只需修改map 函數(shù)的第二個參數(shù)。

比如掂之,我們想操作某個保存“用戶信息”的復合數(shù)據(jù)結構抗俄,把出生月份在9月份之前的用戶年齡增加 1:

=> (map (fn [map-person-info]
          (if (< (:birthmonth map-person-info) 9)
            (assoc map-person-info :age (inc (:age map-person-info)))
            map-person-info)) 
        [{:name "sun" :birthmonth 12 :age 24} {:name "li" :birthmonth 5 :age 20}])

({:name "sun", :birthmonth 12, :age 24} {:name "li", :birthmonth 5, :age 21})

注意這里我們使用了匿名函數(shù) fn。當你想使用這種使用一次就丟棄的“一次性”函數(shù)時板惑,就可以考慮使用匿名函數(shù)橄镜。
不過,Clojure 還提供了一種更為炫酷的匿名函數(shù)形式冯乘,它看起來是這樣子的:

#(+ % 1)
;上面的形式等價于下面的形式
(fn [some-num]
  (+ some-num 1))

不難看出洽胶,其實這種形式就是把參數(shù)列表和參數(shù)名用 % 來代替,然后直接在 #() 里填寫函數(shù)體。如果有多個參數(shù)姊氓,就以 %1 %2 ... 來代替丐怯。
所以上面的給用戶年齡加一的例子使用精簡版匿名函數(shù)來寫,看起來就會是這個樣子:

(map #(if (< (:birthmonth %) 9)
       (assoc % :age (inc (:age %)))
       %) 
     [{:name "sun" :birthmonth 12 :age 24} {:name "li" :birthmonth 5 :age 20}])

不過要注意翔横,匿名函數(shù)的語法糖形式不可嵌套6刘巍!禾唁!fn 則可以嵌套效览。一個原因是,如果你使用非常炫酷的 #() 進行了過多層次的嵌套荡短,可能連你自己也讀不懂丐枉。另一個重要原因是,很難去區(qū)別處理 % 到底是屬于外層還是內(nèi)層掘托。
它們之間還有一些不同瘦锹,鑒于篇幅,你可以自行查閱相關文檔來了解闪盔。


現(xiàn)在出場的是 filter 函數(shù)弯院,正如同它的名字“過濾”,我們可以使用它進行方便的過濾工作泪掀。
比如我們要過濾數(shù)字集合中大于 8 的數(shù)字:

=> (filter #(> % 8) [3 5 426 676 55475 12 4 78 2 48])
(426 676 55475 12 78 48)

filter 函數(shù)的使用方法也很簡單听绳,它的第一個參數(shù)是一個返回值類型是布爾型(boolean)的函數(shù)(也就是返回值是 true 或者 false 的函數(shù)),第二個參數(shù)是待過濾的集合异赫。filter 函數(shù)會一一檢查集合中的元素是否滿足條件辫红,即依次取出集合中的元素作為我們提供的布爾型函數(shù)的參數(shù),把結果為 true 的元素留下祝辣。

再來看一個例子,找到 1 到 10 之間的奇數(shù):

=> (defn odd-number? ; Clojure 里把返回布爾型的函數(shù)命名為 xx? 的形式 
     [number]
     (not= (mod number 2) 0)) ; 除以2余數(shù)不為0的數(shù)字即為奇數(shù)
#'user/odd-number?
=> (filter odd-number? (range 1 11))
(1 3 5 7 9)
; 事實上 Clojure 已經(jīng)提供了 odd? 函數(shù)切油,和我們自己實現(xiàn)的版本功能上一樣
=> (filter odd? (range 1 11)) 
(1 3 5 7 9)

如果加強一點蝙斜,還可以找到質(zhì)數(shù):

=> (defn prime?
     [number]
     (empty? (filter #(= 0 (mod number %)) (range 2 number))))
#'user/prime?
=> (filter prime? (range 1 101))
(1 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97)

當然我們這個函數(shù)的速度還有很大的進步空間,進一步優(yōu)化就交給有興趣的同學了澎胡。


最后我們來看看 reduce 函數(shù)孕荠,文字上很難描述它的功能,不過看了它的例子之后會發(fā)現(xiàn)它也是很容易的攻谁,這個例子是簡單的加法:

=> (reduce + [1 2 3 4 5])
15

雖然我們的 + 函數(shù)支持多個參數(shù)稚伍,但是我們假設加法函數(shù)只支持兩個數(shù)字的加法,那么就需要使用 reduce 函數(shù)來完成多參數(shù)的加法了戚宦。它的執(zhí)行過程是這樣的:

  1. 首先取出集合的前兩個元素作為 + 的參數(shù)个曙,執(zhí)行函數(shù)得到返回值 3
  2. 然后把上一步驟得到的返回值 3,和集合的第三個元素 3受楼,作為 + 的參數(shù)垦搬,執(zhí)行函數(shù)得到返回值 6
  3. 然后把上一步驟得到的返回值 6呼寸,和集合的第四個元素 4,作為 + 的參數(shù)猴贰,執(zhí)行函數(shù)得到返回值 10
  4. ...
  5. 直到集合中的所有元素都被處理对雪,返回最終返回值 15。

這種把上一次的結果和下一個元素作為接下來的函數(shù)參數(shù)米绕,重復直到遍歷元素的過程瑟捣,稱為“規(guī)約”。
由于這種特性栅干,它第二個參數(shù)接受的函數(shù)必須支持傳遞兩個參數(shù)迈套。

我們還可以給規(guī)約過程提供一個初始值,比如下面這個例子非驮,可以把一個集合中的元素添加進另一個集合中:

=> (reduce conj [1 3] [1 2 3])
[1 3 1 2 3]

這個例子中交汤,[1 3] 是初始值,第一次執(zhí)行會從 [1 2 3] 中取出第一個元素 1 劫笙,通過 conj 添加進 [1 3] 中芙扎,得到結果 [1 3 1],以此類推填大,最終結果是 [1 3 1 2 3]戒洼。


最后總結,在函數(shù)式語言中允华,遍歷是聲明式的圈浇,你無需控制遍歷過程,只需使用相應的高階函數(shù)靴寂,往高階函數(shù)中傳遞不同的函數(shù)磷蜀,再通過函數(shù)之間靈活自由的組合,即可輕松應對百炬。
實際上褐隆,往往需要把本次介紹的函數(shù)結合起來使用,以此應對更為復雜的問題剖踊。
比如先使用 map 進行初步處理庶弃,再使用 filter 過濾,最后使用 reduce 規(guī)約德澈,得到最終結果歇攻。


  1. 聲明式編程:告訴程序你想要的是什么,剩下的交給程序來自動處理梆造。命令式編程:一步一步的命令程序如何操作缴守,程序會按照你的命令去進行操作。 ?

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斧散,隨后出現(xiàn)的幾起案子供常,更是在濱河造成了極大的恐慌,老刑警劉巖鸡捐,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栈暇,死亡現(xiàn)場離奇詭異,居然都是意外死亡箍镜,警方通過查閱死者的電腦和手機源祈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來色迂,“玉大人香缺,你說我怎么就攤上這事⌒” “怎么了图张?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诈悍。 經(jīng)常有香客問我祸轮,道長,這世上最難降的妖魔是什么侥钳? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任适袜,我火速辦了婚禮,結果婚禮上舷夺,老公的妹妹穿的比我還像新娘苦酱。我一直安慰自己,他們只是感情好给猾,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布疫萤。 她就那樣靜靜地躺著,像睡著了一般敢伸。 火紅的嫁衣襯著肌膚如雪给僵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天详拙,我揣著相機與錄音,去河邊找鬼蔓同。 笑死饶辙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的斑粱。 我是一名探鬼主播弃揽,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矿微?” 一聲冷哼從身側響起痕慢,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涌矢,沒想到半個月后掖举,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡娜庇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年塔次,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片名秀。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡励负,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匕得,到底是詐尸還是另有隱情继榆,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布汁掠,位于F島的核電站略吨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏调塌。R本人自食惡果不足惜晋南,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羔砾。 院中可真熱鬧负间,春花似錦、人聲如沸姜凄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽态秧。三九已至董虱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間申鱼,已是汗流浹背愤诱。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捐友,地道東北人淫半。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像匣砖,于是被迫代替她去往敵國和親科吭。 傳聞我的和親對象是個殘疾皇子昏滴,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容