莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          幾行代碼解決淘寶面試題之Clojure版

          Posted on 2010-07-15 11:19 dennis 閱讀(8101) 評論(10)  編輯  收藏 所屬分類: javaClojure
              我估計我不寫這樣的標(biāo)題,吸引不了人氣。問題的起因是Javaeye的一個帖子《淘寶面試題:如何充分利用多核CPU,計算很大的 List中所有整數(shù)的和》,看見為了這么個問題寫長長的Java代碼,讓我十分蛋疼。為了表示蛋定,我想介紹下用Clojure解決這個問題的方法。

              題目很明確了,要你充分多核,這年頭不“多核”不好意思出去跟人打招呼,為了多核,你要將list拆分下,每個子list并發(fā)去計算和,然后綜合這些結(jié)果求出最終的和,您沒搞錯,這不是傳說中人見人愛的MapReduce嗎?你沒聽過?不好意思,你out了。

              咱們先別著急并發(fā),先搞個不并發(fā)的版本試試看,第一步,切分list,實在不好意思,clojure有現(xiàn)成的clojure.core/partition函數(shù):
          user=> (partition 3 3 [0'(1 2 3 4 5 6 7 8 9 10))
          ((1 2 3) (4 5 6) (7 8 9) (10 0))

             牛刀小試,將(1 2 3 4 5 6 7 8 9 10)切分成了3個子串,還有個可憐的犀利哥——10號同學(xué)沒辦法歸入任意一個組,只好讓他跟虛無的0為伴了。partition的第三個參數(shù)指定一個湊伙的集合,當(dāng)無法完全切分的時候,拿這個集合里的元素湊合。但是我們不能隨便湊合啊,隨便湊合加起來結(jié)果就不對了,用0就沒問題了。

             切分完了,計算子集的和,那不要太簡單,該reduce同學(xué)上場,請大家歡呼、扔雞蛋,千萬別客氣:
          user=> (reduce + 0 '(1 2 3))
          6
            
              自然要reduce,總要指定規(guī)則怎么reduce,我們這里很簡單,就是個加法運算,再給初始值0就好咯,reduce萬歲。

              慢著,有個問題,partition返回的是集合的集合((1 2 3) (4 5 6) (7 8 9) (10 0)),而上面的reduce卻要作用在子集里,怎么辦?無敵的map大神出場了,map的作用是將某個函數(shù)作用于集合上,并且返回作用后的集合結(jié)果,這里要干的事情就是將上面的reduce作用在partition返回的集合的集合上面:

          user=> (map #(reduce + 0 % )(partition 3 3 [0'(1 2 3 4 5 6 7 8 9 10)))            
          (6 15 24 10)

              返回的是4個子集各自的和,答案肯定沒錯,最后一個結(jié)果不正是唯一的元素10嗎?這里可能比較費解的是#(reduce + 0 %),這其實定義了一個匿名函數(shù),它接收一個參數(shù),這個參數(shù)用百分號%默認(rèn)指代,因為是將map作用于集合的集合,因此這里的%其實就是各個子集。

             map返回的是個集合,又要求集合的總和,是不是又該reduce上場了?不好意思,map同學(xué),這次演出你就一跑龍?zhí)椎模?br />
          user=> (reduce + 0 (map #(reduce + 0 % )(partition 3 3 [0'(1 2 3 4 5 6 7 8 9 10))))
          55

              偉大的55出來了,它不是一個人在戰(zhàn)斗,這一刻LISP、Scheme、Erlang、Scala、Clojure、JavaScript靈魂附體,它繼承了FP的光榮傳統(tǒng),干凈漂亮地解決了這個問題。

              綜合上面所述,我們給出一個非多核版本的解答:
          (defn mysum [coll n]
               (let [sub
          -colls   (partition n n [0] coll)
                     result
          -coll (map #(reduce + 0 %) sub-colls) ]
                   (reduce 
          + 0 result-coll)))
             
              我們是使用了let語句綁定變量,純粹是為了更好看懂一些。sub-colls綁定到partition返回的集合的集合,result-coll就是各個子集的結(jié)果組成的集合,#(reduce + 0 %)是個匿名函數(shù),其中%指向匿名函數(shù)的第一個參數(shù),也就是每個子集。最終,利用reduce將result-coll的結(jié)果綜合在一起。

              “我們要多核,我們要多核,我們不要西太平洋大學(xué)的野雞MapReduce"。

               臺下別激動,神奇的“多核”馬上出場,我要改動的代碼只是那么一點點,用pmap替代map
          (defn psum [coll n]
               (let [sub
          -colls   (partition n n [0] coll)
                     result
          -coll (pmap #(reduce + 0 %) sub-colls) ]
                   (reduce 
          + 0 result-coll)))

             完了嗎?真完了,你要改動的只有這么一點點,就可以讓切分出來的子集并發(fā)地計算了。(感謝網(wǎng)友@clojans的提醒)。

          以下是原文:
              首先是匿名函數(shù)改造一點點:
              我干嘛了,我就加了個future,給你個未來。嚴(yán)肅點地說,future將啟動一個單獨的線程去reduce子集。現(xiàn)在result-coll里不再是直接的結(jié)果,而是各個子集的Future對象,為了得到真正的和,你需要等待線程結(jié)束并取得結(jié)果,因此最后的reduce也要小小地改動下:
          (reduce #(+ %1 @%20 result-coll))

              reduce不再是簡單地用加號了,替代的則是一個兩個參數(shù)的匿名函數(shù),第二個參數(shù)%2是Future對象,我們通過@操作符等待Future返回結(jié)果,并跟第一個參數(shù)%1(初始為0)作加法運算。

              最終的多核版本:
          (defn mysum2 [coll  n]
              (let [sub
          -colls   (partition n n [0] coll)
                    result
          -coll (map #(future (reduce + 0 %)) sub-colls)] 
                   (reduce #(
          + %1 @%20 result-coll)))

             這個多核版本跟非多核版本區(qū)別大嗎?不大嗎?大嗎?不大嗎?……
             可以看到,Clojure可以多么容易地在并發(fā)與非并發(fā)之間徘徊,習(xí)慣腳踏N只船了。


             

          評論

          # re: 幾行代碼解決淘寶面試題之Clojure版[未登錄]  回復(fù)  更多評論   

          2010-07-15 13:24 by 楊一
          我真的很擔(dān)心FP太強大了最后變成matlab

          # re: 幾行代碼解決淘寶面試題之Clojure版[未登錄]  回復(fù)  更多評論   

          2010-07-15 14:06 by k
          蛋疼

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2010-07-15 15:55 by subnet
          威~~~武~~~!

          請問能否做下性能對比?

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2010-07-16 09:20 by t
          語法真難看

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2010-07-16 13:38 by clojans
          直接使用pmap不就ok了嗎?

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2010-07-16 13:57 by dennis
          @clojans
          嗯,用pmap更簡單,我沒有想到,不好意思,接觸clojure才一個多星期。

          # re: 幾行代碼解決淘寶面試題之Clojure版[未登錄]  回復(fù)  更多評論   

          2011-01-18 22:46 by feng
          clojure 確實很強大,亦學(xué)習(xí)clojure中。

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2013-08-23 23:06 by e3rp4y
          (reduce + 0 '(1 2 3)) => (apply + '(1 2 3))

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2013-09-27 20:34 by paomian
          @t
          哪里難看了?習(xí)慣問題

          # re: 幾行代碼解決淘寶面試題之Clojure版  回復(fù)  更多評論   

          2015-12-16 15:19 by gjl
          clojure實在太強大了...
          主站蜘蛛池模板: 万年县| 九龙坡区| 永福县| 曲松县| 贡觉县| 繁昌县| 苍梧县| 阿鲁科尔沁旗| 太原市| 永和县| 平度市| 邛崃市| 安顺市| 宜君县| 廉江市| 文登市| 连云港市| 霍林郭勒市| 曲麻莱县| 巫山县| 咸宁市| 库伦旗| 乌鲁木齐市| 勐海县| 仪征市| 海淀区| 菏泽市| 阿瓦提县| 绥阳县| 延寿县| 梁平县| 木里| 上犹县| 大竹县| 沽源县| 绥滨县| 邹城市| 鹿泉市| 闻喜县| 宁德市| 平舆县|