33

我应该如何监视clojure中映射函数的进度?进展报告的习惯性clojure?

当使用命令式语言处理记录时,我经常会每隔一段时间打印一条消息以指示事情已经走了多远,例如,每1000条记录报告一次。基本上这是计数循环重复。

我想知道我可以采取什么办法,以clojure在哪里我映射函数在我的序列记录。在这种情况下,打印消息(甚至保持进度的计数)似乎基本上是副作用。

我已经想出到目前为止是这样的:

(defn report 
    [report-every val cnt] 
    (if (= 0 (mod cnt report-every)) 
    (println "Done" cnt)) 
    val) 

(defn report-progress 
    [report-every aseq] 
    (map (fn [val cnt] 
      (report report-every val cnt)) 
     aseq 
     (iterate inc 1))) 

例如:

user> (doall (report-progress 2 (range 10))) 
Done 2 
Done 4 
Done 6 
Done 8 
Done 10 
(0 1 2 3 4 5 6 7 8 9) 

正在实现这种效果的还有其他(更好)的方式?

我在做什么有什么陷阱? (我认为我保留了懒惰,而不是头脑风暴)

回答

32

关于clojure的好处是您可以将报告附加到数据本身而不是执行计算的代码。这可以让你分开这些逻辑上不同的部分。下面是从我的misc.clj,我觉得我几乎在每个项目中使用大块:

(defn seq-counter 
    "calls callback after every n'th entry in sequence is evaluated. 
    Optionally takes another callback to call once the seq is fully evaluated." 
    ([sequence n callback] 
    (map #(do (if (= (rem %1 n) 0) (callback)) %2) (iterate inc 1) sequence)) 
    ([sequence n callback finished-callback] 
    (drop-last (lazy-cat (seq-counter sequence n callback) 
        (lazy-seq (cons (finished-callback)())))))) 

然后包裹周围的记者你的数据,然后将结果传递给处理功能。

(map process-data (seq-counter inc-progress input)) 
+1

我想我正在做一些与上面类似的事情,将报告附加到可以完成任何事情的seq。我设想将它附加到一系列结果中,但它同样可以是输入序列。 虽然你的代码更好。我没有取得进展(赦免双关语言)使用报告消息的回调(或更通用的功能),并且我正在为每个值调用报告功能。 – 2010-01-07 20:33:53

+1

是否有你分享misc.clj的任何地方?我肯定会从看到seq-counter – 2010-01-07 20:36:42

+1

等有用的东西的其他想法和实现中受益,是的,它与您的初始示例相同,我对“misk.clj中的ohh thats”有一点点了解,但没有正确理解问题。 http://code.google.com/p/cryptovide/source/browse/src/com/cryptovide/misc.clj。 – 2010-01-07 22:05:35

4

我不知道这样做的任何现有的方式,也许这将是浏览clojure.contrib文档,看看是否已经存在的东西是个好主意。与此同时,我看了你的例子,并稍微澄清了一点。

(defn report [cnt] 
    (when (even? cnt) 
    (println "Done" cnt))) 

(defn report-progress [] 
    (let [aseq (range 10)] 
    (doall (map report (take (count aseq) (iterate inc 1)))) 
    aseq)) 

即使这个例子太简单,你仍然朝着正确的方向前进。这给了我一个关于你的报告 - 进度函数的更一般化版本的想法。该函数将采用类似map的函数,要映射的函数,报表函数和一组集合(或用于测试reduce的种子值和集合)。

(defn report-progress [m f r & colls] 
    (let [result (apply m 
       (fn [& args] 
        (let [v (apply f args)] 
        (apply r v args) v)) 
       colls)] 
    (if (seq? result) 
     (doall result) 
     result))) 

该seq?部分是只为使用减少,而不是 必然返回一个序列。有了这个功能,我们可以重写你的 例子是这样的:

user> 
(report-progress 
    map 
    (fn [_ v] v) 
    (fn [result cnt _] 
    (when (even? cnt) 
     (println "Done" cnt))) 
    (iterate inc 1) 
    (range 10)) 

Done 2 
Done 4 
Done 6 
Done 8 
Done 10 
(0 1 2 3 4 5 6 7 8 9) 

测试过滤功能:

user> 
(report-progress 
    filter 
    odd? 
    (fn [result cnt] 
    (when (even? cnt) 
     (println "Done" cnt))) 
    (range 10)) 

Done 0 
Done 2 
Done 4 
Done 6 
Done 8 
(1 3 5 7 9) 

即使精简函数:

user> 
(report-progress 
    reduce 
    + 
    (fn [result s v] 
    (when (even? s) 
     (println "Done" s))) 
    2 
    (repeat 10 1)) 

Done 2 
Done 4 
Done 6 
Done 8 
Done 10 
12 
+1

我不认为你明白我想要做什么'doall'(对于糟糕和不明确的代码抱歉)。我只是在使用doall来测试repl上的报告,以强制报告处理整个序列(否则它将被懒惰地评估)。多尔不是我试图报告功能或预期序列处理的一部分。 – 2010-01-09 19:11:59

6

我可能会执行在代理中报告。这样的事情:

(defn report [a] 
    (println "Done " s) 
    (+ 1 s)) 

(let [reports (agent 0)] 
    (map #(do (send reports report) 
      (process-data %)) 
     data-to-process) 
+1

这是一个有趣的方法。奇怪的是,如果我在emacs中使用slime-mode,但是在正常repl中打印,则报告不会显示在我的repl中。 – 2010-01-08 21:18:22

+1

经过进一步的思考,我可以增加发送给代理的函数中的内容。进度的打印可以是访问代理状态的repl上的常规功能。 – 2010-01-08 21:41:48

+1

其实好点。事实上,如果你正在更新一个GUI,你可能必须在主线程中完成它(或者推迟到主线程,dispatchLater等)。 – Dan 2010-01-11 14:00:58

0

我有一些运行缓慢的应用程序(例如数据库ETL等)的这个问题。我通过添加功能(tupelo.misc/dot ...)to the tupelo library来解决它。示例:

(ns xxx.core 
    (:require [tupelo.misc :as tm])) 

(tm/dots-config! {:decimation 10}) 
(tm/with-dots 
    (doseq [ii (range 2345)] 
    (tm/dot) 
    (Thread/sleep 5))) 

输出:

 0 .................................................................................................... 
    1000 .................................................................................................... 
    2000 ................................... 
    2345 total 

为tupelo.misc命名空间can be found here API文档。

相关问题