2012-07-11 89 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)。