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

Clojure中关于堆和垃圾的初学者问题,clojure,heap,primes,tail-recursion,Clojure,Heap,Primes,Tail Recursion,我有一个关于Clojure的问题: 我正试图通过阅读来学习这门语言,但我不明白到底发生了什么:下面的代码设计用于返回所有质数的列表,这些质数的最大值为lim。我认为它应该是堆空间中的O(n),因为我列出了一个到lim的所有数字的列表,然后在将第一个数字移动到新列表时逐个过滤掉它们。(我知道我每次都在制作新的列表,但我没想到它们会占用更多的内存?) (defn getAllPrimes [lim] (defn getPrimes [primes numlist] (if (not-e

我有一个关于Clojure的问题: 我正试图通过阅读来学习这门语言,但我不明白到底发生了什么:下面的代码设计用于返回所有质数的列表,这些质数的最大值为
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)))

所以我想我一定不明白列表是如何在调用recur时被垃圾收集的,实际上是在使用O(n*n)heap之类的东西。

我认为问题在于,每次recur都会创建一个新的惰性序列,引用最后一个,因此,经过几次迭代后,你持有的是一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个持有一个。。。所有的中间序列都填满了你的堆

尽管编写素数筛是一个值得的练习,但如果您想得到答案,Clojure确实在其标准库中包含素数序列:Clojure.contrib.lazy-seqs/primes。标准的解决方案是单列线欧拉问题

作为一个风格点,内部定义不是一个好主意。实际效果与defn处于顶层时相同,但如果我没有弄错的话,每次调用getAllPrimes时都会重新分配var,因此强烈建议不要在运行时重新定义var。因为代码只是定义了一个var,所以getPrimes仍然和getAllPrimes一样可见。在这种情况下,getprime可以很容易地重写为循环/重复,没有内部函数、匿名函数或命名函数。这无助于解决一系列懒惰的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 prime

然而,回到实际问题上来,为了让代码正常工作,您所能做的最少工作就是在每次迭代中强制执行每个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)))

这是前面的回答:简短的回答是filter创建了一个惰性序列,您正在调用filter over filter over。。。最后是堆栈溢出。解决这个问题的一个方法(正如dreish所建议的)是用一个“doall”实现每一步的顺序。谢谢,我很欣赏这个答案和风格指针。这非常非常有帮助。我已经用内部defn编写了很多函数,将来我会使用循环。我也知道库中的primes函数,但那时我就不知道过滤器是如何工作的以及为什么我需要一个do all:)。