2012-09-30 61 views
5

給定一系列項目,我想找到n個最頻繁的項目,按頻率降序排列。所以例如我想這個單元測試通過:習慣性的Clojure方法在seq中尋找最頻繁的項目

(fact "can find 2 most common items in a sequence" 
     (most-frequent-n 2 ["a" "bb" "a" "x" "bb" "ccc" "dddd" "dddd" "bb" "dddd" "bb"]) 
     => 
     '("bb" "dddd")) 

我對Clojure相當新,仍然試圖抓住標準庫。這是我想出的:

(defn- sort-by-val [s]  (sort-by val s)) 
(defn- first-elements [pairs] (map #(get % 0) pairs)) 

(defn most-frequent-n [n items] 
    "return the most common n items, e.g. 
    (most-frequent-n 2 [:a :b :a :d :x :b :c :d :d :b :d :b]) => 
     => (:d :b)" 
    (take n (-> 
      items    ; [:a :b :a :d :x :b :c :d :d :b :d :b] 
      frequencies   ; {:a 2, :b 4, :d 4, :x 1, :c 1} 
      seq     ; ([:a 2] [:b 4] [:d 4] [:x 1] [:c 1]) 
      sort-by-val   ; ([:x 1] [:c 1] [:a 2] [:b 4] [:d 4]) 
      reverse    ; ([:d 4] [:b 4] [:a 2] [:c 1] [:x 1]) 
      first-elements))) ; (:d :b :a :c :x) 

但是,這似乎是一個複雜的功能鏈,做一個相當普通的操作。有沒有更優雅或更習慣(或更有效率)的方式來做到這一點?

回答

8

正如您已經發現的那樣,通常您會使用排序和頻率組合來獲取頻率排序列表。

(sort-by val (frequencies ["a" "bb" "a" "x" "bb" "ccc" "dddd" "dddd" "bb" "dddd" "bb"])) 
=> (["x" 1] ["ccc" 1] ["a" 2] ["dddd" 3] ["bb" 4]) 

然後你可以很容易地操縱這個來得到最低/最高頻率的項目。也許是這樣的:

(defn most-frequent-n [n items] 
    (->> items 
    frequencies 
    (sort-by val) 
    reverse 
    (take n) 
    (map first))) 

這又是非常相似的解決方案(除了你不巧妙地利用了->>宏需要的輔助功​​能)。

總的來說,我認爲你的解決方案非常好。不要擔心功能鏈 - 對於邏輯上相當複雜的概念來說,它實際上是一個非常短的解決方案。嘗試在C#/ Java中編碼相同的東西,你會明白我的意思......

+1

謝謝Mikera,你的解決方案是一個很好的改進。 (1)我看到如何正確使用箭頭宏以避免需要輔助函數。 (2)'sort-by'可以直接處理頻率結果,而不需要先執行seq。 (3)標準庫中有一個「第一」功能,所以我不需要創建自己的。 (4)在'map'之前執行'take'可能更有效。 –

+5

'(反向(排序由F COL))'是非常昂貴的,沒有真正的原因 - 寧願改爲'(sort-by(comp -f)coll)'。此外,我會一致的關於你是否使用'first'和'second'或'key'和'val',因爲它們與map條目是等價的。 – amalloy

相關問題