Clojure - 从seq中的向量计数唯一值

时间:2012-07-11 12:33:11

标签: clojure

对于Clojure来说有点新鲜我似乎无法弄清楚如何做一些看起来应该很简单的事情。我只是看不到它。我有一个矢量seq。假设每个向量具有表示客户编号和发票编号的两个值,并且每个向量表示项目的销售。所以它看起来像这样:

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

我想计算独特客户和独特发票的数量。因此该示例应生成向量

[ 2 3 ]

在Java或其他命令式语言中,我将循环遍历seq中的每个向量,将客户编号和发票编号添加到集合中,然后计算每个集合中的值的数量并将其返回。我看不到这样做的功能性方法。

感谢您的帮助。

编辑:我应该在我原来的问题中指出,矢量的序列是数百万的,实际上只有两个值。所以我想只通过seq一次,并计算这些独特的计数(以及一些总和),通过seq运行。

5 个答案:

答案 0 :(得分: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版本中,它足以为价格添加一个参数来循环参数)。所以我同意@ammaloy,对于更简单的情况reduce更好,但更复杂的事情需要更多的低级结构,例如loop/recur对。

答案 1 :(得分:9)

与使用序列时的情况一样,reduceloop更好。你可以这样做:

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

或者,如果你真的陷入过渡期:

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

这两个解决方案只遍历输入一次,并且它们比循环/重复解决方案所需的代码少得多。

答案 2 :(得分:4)

或者您可以使用集合来处理重复数据删除,因为集合最多可以包含任何特定值。

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

答案 3 :(得分:1)

这是使用地图和更高阶函数执行此操作的好方法:

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

=> (2 3)

答案 4 :(得分:0)

上面提到的其他解决方案的其他解决方案:

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

用线程最后一个宏编写的其他内容:

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

都返回(2 3)。