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

Dav*_*aid 7 clojure

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

([ 100 2000 ] [ 100 2000 ] [ 101 2001 ] [ 100 2002 ])
Run Code Online (Sandbox Code Playgroud)

我想计算独特客户和独特发票的数量.所以这个例子应该产生矢量

[ 2 3 ]
Run Code Online (Sandbox Code Playgroud)

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

谢谢您的帮助.

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

ffr*_*end 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))])
Run Code Online (Sandbox Code Playgroud)

请注意,这里首先获得向量的第一个和第二个元素的列表(映射第一个/第二个向量),然后分别对每个元素进行操作,从而迭代收集两次.如果性能确实很重要,那么你可以用迭代(参见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]
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,不需要原子或类似的东西.首先,将状态传递给每个下一次迭代(重复调用).其次,您使用瞬态来使用临时可变集合(有关详细信息,请参阅有关瞬态的更多信息),从而避免每次都创建新对象.

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]))
Run Code Online (Sandbox Code Playgroud)

在这里,我们在矢量中保存中间结果[custs invs total],解压缩,处理并每次将它们打包回矢量.正如您所看到的,实现这样的非平凡逻辑reduce更难(写入和读取)并且需要更多代码(在looped版本中,它足以为循环args添加一个更多的参数).因此,我同意@ammaloy对于更简单的情况reduce更好,但更复杂的事情需要更多的低级构造,例如loop/recurpair.

  • 首先,您的方法是必要的,而且由于 Clojure 主要是函数式语言,在更复杂的情况下,您可能会遇到小问题。使用语言主要范式总是更好,只是因为有更多的工具可以用于其风格的编程。其次,您使用同步原语,这在这里完全没有必要:在函数式语言中,您使用递归而不是显式循环(如 Java 中的“while”)并在将 vars 传递到下一个 recur 步骤时更改状态(参见我的示例中的 args to recur )。此外,同步对于系统来说可能非常昂贵。其余的或多或少是相同的。 (2认同)

ama*_*loy 9

正如消耗序列时经常出现的情况一样,reduceloop这里更好.你可以这样做:

(map count (reduce (partial map conj) 
                   [#{} #{}]
                   txn))
Run Code Online (Sandbox Code Playgroud)

或者,如果你真的喜欢瞬态:

(map (comp count persistent!)
     (reduce (partial map conj!) 
             (repeatedly 2 #(transient #{}))
             txn))
Run Code Online (Sandbox Code Playgroud)

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