繁体   English   中英

Clojure中的竹内数字(表演)

[英]Takeuchi numbers in Clojure (performance)

在计算Takeuchi数字时 ,我们需要计算出函数调用自身的次数。 我很快想出来:

(def number (atom 0))

(defn tak [x y z]
  (if (<= x y)
    y
    (do 
      (dosync (swap! number inc))
      (tak (tak (dec x) y z)
           (tak (dec y) z x)
           (tak (dec z) x y)))))

(defn takeuchi_number [n]
  (dosync (reset! number 0))
  (tak n 0 (inc n))
  @number)

(time (takeuchi_number 10))
; 1029803
; "Elapsed time: 11155.012266 msecs"

但表现非常糟糕。 如何在Clojure中快速制作它?

正如有人所说,删除dosync似乎可以将事情提高10倍,但这不是全部。 一旦JVM热插入您的代码,它就会更快地获得10倍的因子。 这就是为什么你应该使用标准或类似的测试现实世界的速度...

(def number (atom 0))

(defn tak [x y z]
  (if (<= x y)
    y
    (do 
      (swap! number inc)
      (tak (tak (dec x) y z)
           (tak (dec y) z x)
           (tak (dec z) x y)))))

(defn takeuchi_number [n]
  (reset! number 0)
  (tak n 0 (inc n))
  @number)

;=> (time (takeuchi_number 10))
; "Elapsed time: 450.028 msecs"
; 1029803
;=> (time (takeuchi_number 10))
; "Elapsed time: 42.008 msecs"
; 1029803

在我的机器上使用dosync的原装约为5s,所以我们已经订购了两个基本10级的订单! 这是我们能做的最好的吗? 让我们重构纯函数并远离计数器。

(defn tak [c x y z]
  (if (<= x y)
    [c y]
    (let [[a- x-] (tak 0 (dec x) y z)
          [b- y-] (tak 0 (dec y) z x)
          [c- z-] (tak 0 (dec z) x y)]
      (recur (+' 1 a- b- c- c) x- y- z-))))

(defn takeuchi_number [n]
   (tak 0 n 0 (inc n)))

;=> (time (takeuchi_number 10))
; "Elapsed time: 330.741 msecs"
; [1029803 11]
;=> (time (takeuchi_number 10))
; "Elapsed time: 137.829 msecs"
; [1029803 11]
;=> (time (takeuchi_number 10))
; "Elapsed time: 136.866 msecs"
; [1029803 11]

不太好。 将状态保持在向量中并将其传递的成本可能是开销。 但是,现在我们已经重构了纯度,让我们利用我们的良好行为!

=> (def tak (memoize tak))
#'euler.tak/tak
=> (time (takeuchi_number 10))
"Elapsed time: 1.401 msecs"
[1029803 11]

健康快3000左右。 适合我。

实现这一功能的一种纯函数方式是让你的tak函数返回一对[result count] ,其中resulttak计算的实际结果, count是函数递归调用自身的次数。 但在这种情况下,我认为这会导致功能体内各种各样的痛苦扭曲,并且不值得。

这里使用atom ,而习惯性的Clojure,会带来不必要的开销; 它真正的目标是将独立更新同步到线程之间的共享状态。 基本上你想要的是一个可变对象,你可以在同一个线程中传递给递归函数调用,不需要同步。 数组应足以满足此目的:

(defn tak [x y z ^longs counter]
  (if (<= x y)
    y
    (do 
      (aset counter 0 (inc (aget counter 0)))
      (tak (tak (dec x) y z counter)
           (tak (dec y) z x counter)
           (tak (dec z) x y counter)
           counter))))

(defn takeuchi_number [n]
  (let [counter (long-array [0])]
    (tak n 0 (inc n) counter)
    (aget counter 0)))

请注意,我已将计数器定义从作为全局常量移动到辅助函数的参数,以确保可变状态仅在该函数内本地使用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM