34

我应该如何监控 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)

还有其他(更好的)方法可以达到这种效果吗?

我在做什么有什么陷阱吗?(例如,我想我是在保持懒惰而不是抱着头。)

4

4 回答 4

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))
于 2010-01-07T20:17:53.817 回答
6

我可能会在代理中执行报告。像这样的东西:

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

(let [reports (agent 0)]
  (map #(do (send reports report)
            (process-data %))
       data-to-process)
于 2010-01-08T11:06:47.197 回答
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)))

序? 部分仅用于 reduce 不一定返回序列。使用此功能,我们可以像这样重写您的示例:

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)

甚至是reduce函数:

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
于 2010-01-07T20:59:12.450 回答
-1

我遇到了一些运行缓慢的应用程序(例如数据库 ETL 等)的问题。我通过将函数添加(tupelo.misc/dot ...) 到 tupelo 库来解决它。样本:

(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 命名空间的 API 文档。

于 2016-10-05T02:32:50.383 回答