首页 > 解决方案 > Clojure - Map 和 Reduce 之间的区别 // 将一个转换为另一个

问题描述

(defn DoubleFrequency [] 
(def s (slurp "Example.txt"))
(def m (reduce #(assoc %1 %2 (inc (%1 %2 0)))
    {}
    (re-seq #".." s)))
(def c (count m))   
(doseq [[k x] m]
    (println k ":" (/ x c))))   

我正在尝试将并发应用到我的程序中,并且我想使用 pmap,但我不确定如何将它应用到我当前的代码中。该功能对于单核是正确的,但理想情况下,我想以某种方式用 pmap 替换 reduce 并获得相同的结果。

标签: concurrencyclojure

解决方案


首先,您要编写的功能被称为frequencies

user> (frequencies [1 2 1 3 1 4 4])
;;=> {1 3, 2 1, 3 1, 4 2}

它确实是单线程的。所以让我们试着让它平行。

最初的方法reduce是正确的方向,虽然它也不是并行的,但它可以用于与 clojure 的标准库并发设施,即reducers进行并行处理。

首先,让我们稍微重写一下你的 reducer 函数,做同样的事情,但以更惯用的方式(它是可选的,但有利于可读性):

#(assoc %1 %2 (inc (%1 %2 0)))=>#(update %1 %2 (fnil inc 0))

然后我们可以使用以下方法进行并行减少fold

(require '[clojure.core.reducers :as r])

(defn pfreq [data]
  (r/fold
   (partial merge-with +)
   (fn [acc k] (update acc k (fnil inc 0)))
   data))

这个想法是它按块拆分您的集合(如果它足够长),然后将块的结果与merge-with

user> (pfreq [1 2 1 3 1 4 1 5 2])
;;=> {1 4, 2 2, 3 1, 4 1, 5 1}

另请注意,该系列应该是“可折叠的”。默认情况下,持久向量和映射是可折叠的,re-seq结果不是,所以你应该先将它转换为 vector: (vec (re-seq #"..x" s)),否则你不会得到任何并行化,回退到 plain reduce

您显然可以使用 pmap 来解决这个问题,使用相同的策略:split -> map -> combine:

(defn pfreq2 [chunk-size data]
  (->> data
       (partition-all chunk-size)
       (pmap frequencies)
       (apply merge-with +)))

但这不像reducers管道那样灵活和强大。


推荐阅读