2012-07-11 91 views
7

對於Clojure來說有點新鮮,我似乎無法弄清楚如何做一些看起來應該很簡單的事情。我只是看不到它。我有一個向量序列。假設每個向量都有兩個代表客戶編號和發票編號的值,每個向量代表一個項目的銷售。所以它看起來像這樣:Clojure - 從一個seq向量中計算唯一值

([ 100 2000 ] [ 100 2000 ] [ 101 2001 ] [ 100 2002 ]) 

我想統計唯一客戶和唯一發票的數量。因此,例如應該產生載體

[ 2 3 ] 

在Java或其他必要的語言,我會遍歷在NGF的載體中的每一個,增加客戶數量和發票號碼的一組再算上中值的數量每個設置並返回它。我看不到執行此操作的功能。

感謝您的幫助。

編輯:我應該在我原來的問題中指出,向量的seq是在數百萬的實際上有超過兩個值。所以我只想通過一次seq來計算這些唯一的計數(以及一些和數)。

回答

11

在Clojure中,你可以做到這一點幾乎相同的方式 - 第一次調用distinct獲得獨特的值,然後用count計算結果:

(def vectors (list [ 100 2000 ] [ 100 2000 ] [ 101 2001 ] [ 100 2002 ])) 
(defn count-unique [coll] 
    (count (distinct coll))) 
(def result [(count-unique (map first vectors)) (count-unique (map second vectors))]) 

請注意,在這裏你第一次得到的第一和第二的元素列表向量(映射第一/第二向量),然後分別進行操作,從而迭代兩次收集。如果性能確實很重要,則可以使用迭代(請參閱loop表單或尾遞歸)和集合來做同樣的事情,就像在Java中所做的一樣。要進一步提高性能,您還可以使用transients。雖然對於像你這樣的初學者,我會推薦第一種方式distinct

UPD。這裏的版本與循環:

(defn count-unique-vec [coll] 
    (loop [coll coll, e1 (transient #{}), e2 (transient #{})] 
    (cond (empty? coll) [(count (persistent! e1)) (count (persistent! e2))] 
      :else (recur (rest coll) 
         (conj! e1 (first (first coll))) 
         (conj! e2 (second (first coll))))))) 
(count-unique-vec vectors) ==> [2 3] 

正如你所看到的,在原子或類似的東西沒有必要。首先,您將狀態傳遞給每個下一個迭代(重複調用)。其次,您使用瞬變來使用臨時可變的集合(有關詳細信息,請閱讀有關瞬變的更多信息),從而避免每次都創建新對象。

UPD2。這裏的版本reduce延長的問題(與價格):

(defn count-with-price 
    "Takes input of form ([customer invoice price] [customer invoice price] ...) 
    and produces vector of 3 elements, where 1st and 2nd are counts of unique  
    customers and invoices and 3rd is total sum of all prices" 
    [coll] 
    (let [[custs invs total] 
     (reduce (fn [[custs invs total] [cust inv price]] 
        [(conj! custs cust) (conj! invs inv) (+ total price)]) 
      [(transient #{}) (transient #{}) 0] 
      coll)] 
    [(count (persistent! custs)) (count (persistent! invs)) total])) 

在這裏,我們持有一個向量[custs invs total]中間結果,解壓縮,處理和每次收拾他們回到一個載體。正如你所看到的,用reduce實現這樣的平凡邏輯比較困難(既可以寫入也可以讀取),並且需要更多的代碼(在loop ed版本中,足夠爲價格添加一個參數來循環參數)。所以我同意@ammaloy,對於更簡單的情況reduce更好,但更復雜的事情需要更多的低級構造,例如loop/recur對。

+0

謝謝。由於收集的交易數量在幾十萬之內,因此性能確實非常重要。使用循環形式是否需要使用原子或類似的東西來維持循環的每次迭代之間的狀態?這是讓我想起來的部分。 – 2012-07-11 12:57:06

+0

@DaveKincaid:看到我的更新。但是請注意,所有解決方案的時間複雜度都是相同的,所以它們的運行時間只會因恆定乘數(可能非常小)而不同。 – ffriend 2012-07-11 13:13:16

+0

這很棒!謝謝。發佈我的問題並看到您的第一個回覆後。我離開並嘗試了一下。這是我想出來的。我想知道你能否幫助我理解你的方法和這個方法之間的差異。 (讓客戶設置(atom#{})invoice-set(atom#{})](doseq [[customer invoice] txn](swap!customer-set conj customer)(swap!invoice-set conj invoice) )[(count(deref customer-set)))(count(deref invoice-set))])' – 2012-07-11 13:17:53

4

或者您可以使用集合來處理您的重複刪除,因爲集合可以具有任何特定值中的最大值之一。

(def vectors '([100 2000] [100 2000] [101 2001] [100 2002]))  
[(count (into #{} (map first vectors))) (count (into #{} (map second vectors)))] 
9

作爲消耗的序列時是經常發生的情況,reduceloop這裏更好。你可以這樣做:

(map count (reduce (partial map conj) 
        [#{} #{}] 
        txn)) 

或者,如果你真的到瞬變:

(map (comp count persistent!) 
    (reduce (partial map conj!) 
      (repeatedly 2 #(transient #{})) 
      txn)) 

這些解決方案的兩個遍歷輸入唯一的一次,他們採取比環更少的代碼/復發解。

+0

非常好!儘管如此,我還是要把它弄皺。向每個價格添加第三個元素。現在生成一個包含以前計數的矢量,但也會增加價格總和。這可以用類似的乾淨方式來完成嗎? – 2012-07-11 21:20:47

+0

這當然是可能的,減少仍然是最好的方法,但我不會自己寫:P。 – amalloy 2012-07-11 22:21:00

+0

我刺傷了這個。告訴我這有多瘋狂。我創建了一個函數圖(def f-map {0 count 1 count 2(partial reduce +)}),然後使用map-indexed在f-map的相應函數上運行每個函數。像這樣:(map-indexed#((get f-map%1)%2)(reduce(partial map conj)[#[]#[] []] txn)) – 2012-07-11 23:58:40

1

這裏是一個很好的方式與地圖和高階函數來做到這一點:

(apply 
    map 
    (comp count set list) 
    [[ 100 2000 ] [ 100 2000 ] [ 101 2001 ] [ 100 2002 ]]) 

=> (2 3) 
0

而且其他的解決方案,以漂亮的上述那些:

(map (comp count distinct vector) [ 100 2000 ] [ 100 2000 ] [ 101 2001 ] [ 100 2002 ])

其他與thread-寫最後一個宏:

(->> '([100 2000] [100 2000] [101 2001] [100 2002]) (apply map vector) (map distinct) (map count))

都返回(2 3)。