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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/svg/2.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,我在做欧拉计划的第7题(计算第10001个素数)。我已经以惰性序列的形式编写了一个解决方案,但它的速度非常慢,而我在web上找到的另一个解决方案(下面的链接)只需要不到一秒钟的时间就可以完成基本相同的任务 我不熟悉clojure和lazy序列,所以我使用take while、lazy cat rest或map可能是罪魁祸首。你能看一下我的密码并告诉我你看到了什么吗 在一秒钟内运行的解决方案如下: 它不使用惰性序列。我想知道为什么它如此之快而我的却如此之慢(他们遵循的过程是相似的) 我的解决方案

我在做欧拉计划的第7题(计算第10001个素数)。我已经以惰性序列的形式编写了一个解决方案,但它的速度非常慢,而我在web上找到的另一个解决方案(下面的链接)只需要不到一秒钟的时间就可以完成基本相同的任务

我不熟悉clojure和lazy序列,所以我使用take while、lazy cat rest或map可能是罪魁祸首。你能看一下我的密码并告诉我你看到了什么吗

在一秒钟内运行的解决方案如下:

它不使用惰性序列。我想知道为什么它如此之快而我的却如此之慢(他们遵循的过程是相似的)

我的解决方案非常慢:

(def primes 
  (letfn [(getnextprime [largestprimesofar]
    (let [primessofar (concat (take-while #(not= largestprimesofar %) primes) [largestprimesofar])]
      (loop [n (+ (last primessofar) 2)]
          (if
            (loop [primessofarnottriedyet (rest primessofar)]
              (if (= 0 (count primessofarnottriedyet))
                true
                (if (= 0 (rem n (first primessofarnottriedyet)))
                  false
                  (recur (rest primessofarnottriedyet)))))
            n
            (recur (+ n 2))))))]
    (lazy-cat '(2 3) (map getnextprime (rest primes)))))

要尝试它,只需加载它并运行类似(10000个素数),但使用Ctrl+C终止进程,因为它太慢了。但是,如果您尝试(使用100个素数),您应该会得到一个即时的答案。

让我重新编写您的代码,以便将其分解为更易于讨论的部分。我用的是你的算法,我只是把一些内部形式分解成不同的函数

(declare primes)   ;; declare this up front so we can refer to it below

(defn is-relatively-prime? [n candidates]
  (if (= 0 (count candidates))
    true
    (if (zero? (rem n (first candidates)))
      false
      (is-relatively-prime? n (rest candidates)))))

(defn get-next-prime [largest-prime-so-far]
  (let [primes-so-far (concat (take-while #(not= largest-prime-so-far %) primes) [largest-prime-so-far])]
    (loop [n (+ (last primes-so-far) 2)]
      (if
        (is-relatively-prime? n (rest primes-so-far))
        n
        (recur (+ n 2))))))

(def primes
  (lazy-cat '(2 3) (map get-next-prime (rest primes))))

(time (let [p (doall (take 200 primes))]))
最后一行只是为了更容易在REPL中获得一些真正粗略的基准。通过将计时语句作为源文件的一部分,我可以继续重新加载源代码,每次都可以获得一个新的基准测试。如果我只加载一次文件,并继续尝试执行
(取500个素数)
基准将发生倾斜,因为
素数
将保留它已计算的素数。我还需要
doall
,因为我在
let
语句中提取素数,如果我不使用
doall
,它只会将惰性序列存储在
p
中,而不是实际计算素数

现在,让我们得到一些基本值。在我的电脑上,我得到以下信息:

Loading src/scratch_clojure/core.clj... done
"Elapsed time: 274.492597 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 293.673962 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 322.035034 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 285.29596 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 224.311828 msecs"
大约275毫秒,大约50秒。我的第一个怀疑是,到目前为止,我们是如何在
let
语句
中获得
素数的。我们正在浏览一个完整的素数列表(据我们所知),直到找到一个与目前为止最大的素数相等的素数。然而,按照我们构建代码的方式,所有的素数都已按顺序排列,因此我们实际上在遍历除最后一个素数之外的所有素数,然后连接最后一个值。我们得到的值与迄今为止在
素数
序列中实现的值完全相同,因此我们可以跳过整个步骤,只使用
素数
。那应该能帮我们省点什么

我的下一个怀疑是调用循环中的
(到目前为止的最后一个素数)
。当我们在一个序列上使用
last
函数时,它也会从头到尾遍历列表(或者至少,这是我的理解——我不会让Clojure编译器编写人员通过偷偷编写一些特殊情况的代码来加快速度。)但是,我们也不需要它。我们正在使用目前为止最大的素数调用
获取下一个素数
,因为我们的素数是有序的,这已经是我们所认识到的最后一个素数了,所以我们可以使用
到目前为止最大的素数
而不是
(最后一个素数)
。这将给我们带来:

(defn get-next-prime [largest-prime-so-far]
  ; deleted the let statement since we don't need it
  (loop [n (+ largest-prime-so-far 2)]
    (if
      (is-relatively-prime? n (rest primes))
      n
      (recur (+ n 2)))))
这似乎应该会加快速度,因为我们已经消除了素数序列中的两次完整行走。让我们试试看

Loading src/scratch_clojure/core.clj... done
"Elapsed time: 242.130691 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 223.200787 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 287.63579 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 244.927825 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 274.146199 msecs"
嗯,也许稍微好一点(?),但没有我预期的改善。让我们看一下
的代码是否相对优质?
(正如我重新编写的那样)。我首先想到的是
count
函数。
primes
序列是一个序列,而不是一个向量,这意味着
count
函数还必须遍历整个列表,以计算其中有多少元素。更糟糕的是,如果我们从一个列表开始,比如说,10个候选,它第一次遍历所有10个循环,然后遍历下一个循环中剩下的9个候选,然后遍历剩下的8个,依此类推。随着素数的增多,我们将在计数函数中花费越来越多的时间,所以这可能是我们的瓶颈

我们想去掉
计数
,这表明我们可以用一种更惯用的方法来做循环,使用
if let
。像这样:

(defn is-relatively-prime? [n candidates]
  (if-let [current (first candidates)]
    (if (zero? (rem n current))
      false
      (recur n (rest candidates)))
    true))
如果候选列表为空,
(第一个候选)
函数将返回
nil
,如果出现这种情况,
if let
函数将注意到,并自动跳转到else子句,在这种情况下,这是我们返回的结果“true”。否则,我们将执行“then”子句,并且可以测试
n
是否可以被当前候选对象平均整除。如果是,我们将返回false,否则我们将与其他候选人一起返回。我还利用了
zero?
函数,因为我可以。让我们看看这能给我们带来什么

Loading src/scratch_clojure/core.clj... done
"Elapsed time: 9.981985 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 8.011646 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 8.154197 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 9.905292 msecs"
Loading src/scratch_clojure/core.clj... done
"Elapsed time: 8.215208 msecs"
很戏剧性,是吗?我是一名中级Clojure编码员,对其内部结构的理解相当粗略,因此对我的分析持保留态度,但根据这些数字,我猜你被
计数咬了一口


还有另外一个优化,“快速”代码使用的是你没有使用的优化,那就是在
是相对优质的?
测试时,只要
当前的
平方大于
n
——如果你能加入它,你可能会加快你的代码。但是我认为你要找的主要是
count

让我重新编写你的代码,把它分解成更容易讨论的部分。我用的是你的算法,我只是把一些内部形式分解成不同的函数

(declare primes)   ;; declare this up front so we can refer to it below

(defn is-relatively-prime? [n candidates]
  (if (= 0 (count candidates))
    true
    (if (zero? (rem n (first candidates)))
      false
      (is-relatively-prime? n (rest candidates)))))

(defn get-next-prime [largest-prime-so-far]
  (let [primes-so-far (concat (take-while #(not= largest-prime-so-far %) primes) [largest-prime-so-far])]
    (loop [n (+ (last primes-so-far) 2)]
      (if
        (is-relatively-prime? n (rest primes-so-far))
        n
        (recur (+ n 2))))))

(def primes
  (lazy-cat '(2 3) (map get-next-prime (rest primes))))

(time (let [p (doall (take 200 primes))]))
最后一行只是为了更容易在REPL中获得一些真正粗略的基准。通过将计时语句作为源文件的一部分,我可以继续重新加载源代码,每次都可以获得一个新的基准测试。如果我只加载一次文件,然后继续尝试
(defn get-next-prime [largest-prime-so-far]
  (let [primes-so-far (concat (take-while #(not= largest-prime-so-far %) primes) [largest-prime-so-far])]
    (loop [n (+ (last primes-so-far) 2)]
      (if
          (is-relatively-prime? n
                                (take-while #(<= (* % %) n)
                                            (rest primes-so-far)))
        n
        (recur (+ n 2))))))

user> (time (first (drop 10000 primes)))
"Elapsed time: 10564.470626 msecs"
104743
(defn get-next-prime [largest-prime-so-far]
  (loop [n (+ largest-prime-so-far 2)]
    (if (is-relatively-prime? n
          (take-while #(<= (* % %) n) (rest primes)))
      n
      (recur (+ n 2)))))

user> (time (first (drop 10000 primes)))
"Elapsed time: 142.676634 msecs"
104743
user> (time (first (drop 100000 primes)))
"Elapsed time: 2615.910723 msecs"
1299721
(declare primes)

(defn get-next-prime [largest-prime-so-far]
  (loop [n (+ largest-prime-so-far 2)]
    (if (not-any? #(zero? (rem n %))
                  (take-while #(<= (* % %) n)
                              (rest primes)))
      n
      (recur (+ n 2)))))

(def primes
  (lazy-cat '(2 3) (map get-next-prime (rest primes))))
user> (time (first (drop 100000 primes)))
"Elapsed time: 2493.291323 msecs"
1299721