在 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 版本中,为 price 循环添加一个参数就足够了)。所以我同意@ammaloy 的观点,更简单的情况reduce
更好,但更复杂的事情需要更多的低级构造,比如loop/recur
pair。