我对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)堆的垃圾收集列表是什么。
答案 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)))