【问题标题】:Clojure optimization of an inversion counter反转计数器的 Clojure 优化
【发布时间】:2014-05-11 06:22:04
【问题描述】:

我是 Clojure 的新手。我想知道如何优化算法来计算列表中的反转次数。据我了解,除非特别要求,否则 Clojure 不会进行尾调用优化?你如何让它做到这一点?

第一次尝试使用变异变量的运行时间约为 3.5 秒。但我的第二次尝试是功能版本,大约需要 1m15s!并且两者都需要增加相当多的堆栈大小(例如-Xss12m)。

我将如何获得更好的性能?

如果可能的话,我宁愿没有可变变量(比如函数变量)。您可以通过键入 seq 100000 | sort -R > IntArray.txt 之类的内容来创建数组文件。

第一次尝试使用可变变量:

(use 'clojure.java.io)

(def inversions 0)

(defn merge_and_count' [left right left_len]
  (if (empty? right) left
      (if (empty? left) right
          (if (<= (first left) (first right)) 
              (cons (first left)  (merge_and_count' (rest left) right (- left_len 1)))
              (let [_ (def inversions (+ inversions left_len))]
               (cons (first right) (merge_and_count' left (rest right) left_len)))
                  ))))

(defn inversion_count [list]
  (if (or (empty? list) (nil? (next list))) list
      (let [mid (quot (count list) 2)]
           (merge_and_count' (inversion_count (take mid list)) 
                             (inversion_count (drop mid list)) mid)
                                 )))

(defn parse-int [s]
   (Integer. (re-find  #"\d+" s )))

(defn get-lines [fname]
  (with-open [r (reader fname)]
    (doall (map parse-int (line-seq r)))))

(let [list (get-lines "IntArray.txt")
      _ (inversion_count list)]
  (print inversions))

我第二次尝试纯函数式(无可变性):

(use 'clojure.java.io)


(defn merge_and_count' [left right inversions]
  (if (empty? right) (list left inversions)
      (if (empty? left) (list right inversions)
          (if (<= (first left) (first right)) 
              (let [result (merge_and_count' (rest left) right inversions)]
                   (list (cons (first left) (first result)) (second result)))
              (let [result (merge_and_count' left (rest right) (+ inversions (count left)))]
                   (list (cons (first right) (first result)) (second result)))
                       ))))

(defn inversion_count [list' list_len]
  (if (or (empty? list') (nil? (next list'))) (list list' 0)
      (let [mid (quot list_len 2)
            left (inversion_count (take mid list') mid)
            right (inversion_count (drop mid list') (- list_len mid))]
           (merge_and_count' (first left) (first right) (+ (second left) (second right)))
                                 )))

(defn parse-int [s]
   (Integer. (re-find  #"\d+" s )))

(defn get-lines [fname]
  (with-open [r (reader fname)]
    (doall (map parse-int (line-seq r)))))

(let [list (get-lines "IntArray.txt")
      result (inversion_count list 100000)]
  (print (second result)))

【问题讨论】:

  • 这个算法应该做什么?我只看到一大堆firstrestsecond
  • @amalloy 参见here,例如
  • @pete23 他的递归调用不在尾部位置,因此用recur 替换它们将用编译器错误替换糟糕的性能。
  • 我假设我们正在寻求解决这个问题? stackoverflow.com/questions/337664/… 即列表中每个项目的总和,即小于它的后继条目的#。 n^2 解决方案应该是微不足道的。
  • Loop/recur 并不完全是“如何在 Clojure 中启用尾调用优化”——Clojure 中没有尾调用优化。但是循环/递归是提供类似功能的替代方案。文档在这里:clojure.org/special_forms#Special%20Forms--(recur%20exprs*)

标签: optimization clojure mutable


【解决方案1】:

由于合并计数中的递归,堆栈溢出。我尝试了这种方法,对于 100000 个项目,它立即返回。

(defn merge_and_count [left right inversions]
  (loop [l left r right inv inversions result []]
    (cond (and (empty? r) (empty? l)) [result inv]
          (empty? r) [(apply conj result l) inv]
          (empty? l) [(apply conj result r) inv]
          (<= (first l) (first r)) (recur (rest l) r inv (conj result (first l)))
          :else (recur l (rest r) (+ inv (count l))  (conj result (first r))))))

您需要用第二种方法中的代码替换此代码。

【讨论】:

  • 谢谢。那么循环/递归是 Clojure 优化递归/尾递归的事实上的方式吗?你知道为什么 Clojure 没有对递归调用进行隐式优化吗?这似乎是函数式编程的一个非常基本的方面。跟JVM有关系吗?
  • 这里有一些讨论:stackoverflow.com/questions/19462314/…。另外,请查看 Rich Hickey 的评论:groups.google.com/forum/#!msg/clojure/4bSdsbperNE/tXdcmbiv4g0J 通常,recur 允许您执行循环中可能执行的任何操作,但使用函数构造。这有助于进行函数式和递归式思考。
猜你喜欢
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 2020-07-08
  • 2014-03-31
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
相关资源
最近更新 更多