Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure 数学幂函数的延迟序列还是递归?_Clojure - Fatal编程技术网

Clojure 数学幂函数的延迟序列还是递归?

Clojure 数学幂函数的延迟序列还是递归?,clojure,Clojure,作为练习,我实现了数学幂函数。一旦使用recur: (defn power [a n] (let [multiply (fn [x factor i] (if (zero? i) x (recur (* x factor) factor (dec i))))] (multiply a a (dec n)))) user=> (time (dotimes [_

作为练习,我实现了数学幂函数。一旦使用recur:

(defn power [a n]
  (let [multiply (fn [x factor i]
                   (if (zero? i)
                     x
                     (recur (* x factor) factor (dec i))))]
  (multiply a a (dec n))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 1839.406746 msecs"
还有一次,用lazy seq:

(defn power [a n]
  (letfn [(multiply [a factor]
            (lazy-seq
              (cons a (multiply (* a factor) factor))))]
  (nth (multiply a a) (dec n))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 2162.297827 msecs"
您认为哪种实现更优越?我真的不知道。。(我会使用recur,因为它更容易理解。)

我读到lazy seq很快,因为它使用内部缓存。但是我在我的示例中没有看到任何缓存的机会。我是不是忽略了什么

更新
我把时间和样品一起贴了出来。这里的复发似乎稍微快一点

常规递归也不太糟糕:

(defn power [a n]
   (if (== n 1)
      a
      (* a (power a (dec n)))))

user=> (time (dotimes [_ 10000] (power 2 512)))
"Elapsed time: 1877.34521 msecs"

你应该做定时测试,运行1m次并查看时间。通常非递归函数速度更快,但在处理函数式语言时,递归是首选方法,因为它们通常使用尾部调用。Clojure是基于Java Clr的,所以我现在不知道是否支持尾部调用,但如果支持尾部调用,它应该与非递归调用一样快。

首先,通常的建议是先选择,稍后再考虑实现细节(如果您的代码对性能非常敏感,或者可能在不支持的上下文中使用)

还有美学方面的考虑。对我来说,
重现
似乎更干净,因为这是解决问题的最自然的方式。当它们以某种方式进入语义图时,使用序列是有意义的,否则,代码编写/理解/执行会变得非常容易。这里没有这种情况


最后,我肯定希望
recur
总体上更快,因为它避免了不必要的分配&GC。初始计时支持这一点。这里确实没有机会从任何类型的缓存中获益,因为每当调用
power
时,您都会从头开始生成序列,并且在返回后从不保留它。

我在这里提供了一些自己的lazy-power函数,以展示如何从函数中重用lazy-seq

每次用两个数字调用
my simple lazy power
,它都会生成一个幂为某个数字x的lazy seq,并返回其中的第n项。使用这个版本非常昂贵,因为它只为每个函数调用构造一个惰性seq。这可能就是为什么我的简单懒惰能力的基准测试如此之慢的原因。由于lazy-seq缓存了它们的结果,所以您可能希望重用它们。这就是我的惰性幂所做的:它为一个数字x构造一个惰性seq,并围绕它包装一个函数,该函数接受n作为参数。您可以重复使用后一个函数来访问缓存的结果。(只要函数存在,函数就会保留对惰性序列的引用,因为它“关闭”了惰性序列。这就是为什么他们称之为闭包。)

另一种缓存函数结果的常用方法是使用函数的记忆版本。基本上,
memoize
会记住传入参数的结果,因此下次传入完全相同的参数时,它将从缓存返回结果。见下面的例子。为了进行比较,我还对你的版本和它们的记忆版本进行了计时

(defn my-simple-lazy-power [x n]
  (let [my-lazy-list 
    ((fn my-lazy [y] 
       (lazy-cat [y] (map #(* % x) (my-lazy y)))) x)]
    (nth my-lazy-list n)))

(defn my-lazy-power [x]
  (let [my-lazy-list 
    ((fn my-lazy [y] 
       (lazy-cat [y] (map #(* % x) (my-lazy y)))) x)]
    (fn [n]
      (nth my-lazy-list n))))

(defn rec-power [a n]
  (let [multiply (fn [x factor i]
                   (if (zero? i)
                     x
                     (recur (* x factor) factor (dec i))))]
    (multiply a a (dec n))))

(defn lazy-power [a n]
  (letfn [(multiply [a factor]
            (lazy-seq
             (cons a (multiply (* a factor) factor))))]
    (nth (multiply a a) (dec n))))

(def mem-my-simple-power (memoize my-simple-lazy-power))
(def mem-my-power (memoize my-lazy-power))
(def mem-rec-power (memoize rec-power))
(def mem-laz-power (memoize lazy-power))

(time (dotimes [_ 50] (my-simple-lazy-power 2 512)))
"Elapsed time: 7138.346976 msecs"
nil

(time (let [my-pow-2 (my-lazy-power 2)]
  (dotimes [_ 10000] (my-pow-2 512))))
"Elapsed time: 854.717301 msecs"
nil

(time (dotimes [_ 10000] (rec-power 2 512)))
"Elapsed time: 2726.559879 msecs"
nil

(time (dotimes [_ 10000] (mem-rec-power 2 512)))
"Elapsed time: 4.775677 msecs"
nil

(time (dotimes [_ 10000] (lazy-power 2 512)))
"Elapsed time: 3617.100209 msecs"
nil

(time (dotimes [_ 10000] (mem-laz-power 2 512)))
"Elapsed time: 4.95887 msecs"
nil
PS:我不得不在我的版本中围绕lazy seq定义编写
fn
,因为let不支持递归定义,但
fn
支持递归定义


PS2:很抱歉缩进,从Emacs复制粘贴似乎无法保存它…

添加到Michael Marczyck的答案中

您可以将
乘法
函数的定义和调用折叠成
循环

(defn power [a n]
  (loop [x a, factor a, i (dec n)]
   (if (zero? i)
      x
      (recur (* x factor) factor (dec i)))))
。。。但它并没有跑得更快

正如MM所写,选择正确的算法很重要。他建议的那个运行示例的速度大约是你的20倍:

(defn power [x n]
  (loop [acc 1, n n, factor x]
    (if (zero? n)
      acc
      (recur
        (if (even? n) acc (* acc factor))
        (quot n 2)
        (* factor factor)))))
您必须提示当前Clojure使用
BigInt
,否则将出现整数溢出:

(time (dotimes [_ 10000] (power 2N 512)))

您的里程数可能会有所不同。

您也可以使用
*'
而不是
*
来避免整数溢出。