关于Clojure中的堆和垃圾的初学者问题

时间:2010-08-10 23:31:49

标签: clojure heap primes tail-recursion

我对Clojure有疑问: 我试图通过Project Euler来学习这门语言而且我不明白幕后发生的事情:以下代码旨在使用返回所有素数列表,直至lim 。我认为它应该是堆空间中的O(n),因为我列出了所有数字到lim,然后逐个过滤它们,同时将第一个数字移动到新的列表。 (我知道我实际上每个都会重新制作新的列表,但我认为它们不会占用更多内存?)无论如何,我正在使用

(defn getAllPrimes [lim] 
  (defn getPrimes [primes numlist]
    (if (not-empty numlist) ;base case;
    (recur (cons (first numlist) primes) ;put the prime on to the prime list 
           (filter
        (fn [x] (not (div? x (first numlist)))) ;remove the prime and all its multiples from the numlist
        (rest numlist)))
      primes)); return the primes
  (getPrimes () (range 2 lim))) ;call the recursive function with and empty prime list to be filled up and a full numlist to be emptied

当我打电话

时,我的堆空间不断用完
(apply + (getAllPrimes 2000000))

,但我的

空间不足
(apply + (filter even? (range 2000000)))

所以我认为我不能理解在重复调用中实际使用O(n * n)堆的垃圾收集列表是什么。

1 个答案:

答案 0 :(得分:6)

我认为问题在于,每次重复时,你都会创建一个新的延迟序列,指向最后一个,所以在几次迭代之后,你持有一个seq,它包含了一个seq的头部,一个seq,它持有seq的头部。 ......所有中间序列都填满了你的堆。

虽然写一个主要筛子是值得的,但如果你想得到答案,Clojure确实在其标准库中包含了素数序列:clojure.contrib.lazy-seqs / primes。这个特殊欧拉问题的标准解决方案是单线。

作为一种风格点,内在的定义并不是一个好主意。实际效果与defn处于顶层时相同,但如果我没有弄错,每次调用getAllPrimes时都会重新分配var,并且非常强烈建议不要在运行时重新定义变量。由于代码只是定义一个var,getPrimes仍然像getAllPrimes一样可见。在这种情况下,getPrimes可以很容易地重写为没有内部函数,匿名或命名的循环/重复。这对你的懒惰seqs问题没有帮助,但它确实使代码更具标准性:

(defn getAllPrimes [lim]
  (loop [primes ()
         numlist (range 2 lim)]
    (if (not-empty numlist)
      (recur (cons (first numlist) primes)
             (filter (fn [x] (not (div? x (first numlist))))
                     (rest numlist)))
      primes)))

我也会避免使用camelCase。这个函数的Clojure标准名称将是get-all-primes。

回到实际问题,你可以做的最少的工作就是在每次迭代时强制每个seq,即将你的过滤器调用包装在doall中。我尝试了这个,虽然它仍然运行缓慢,但它至少会运行完成而不会耗尽堆:

(defn get-all-primes [lim]
  (loop [primes ()
         numlist (range 2 lim)]
    (if (not-empty numlist)
      (recur (cons (first numlist) primes)
             (doall (filter #(not (div? % (first numlist)))
                            (rest numlist))))
      primes)))