如何在Clojure中构建完全数的惰性序列

  • 本文关键字:Clojure 构建 clojure
  • 更新时间 :
  • 英文 :


我试着用这种方法找到一个完全数列表:

(defn classify [num] 
  (let [factors (->> (range 1 (inc num))
                     (filter #(zero? (rem num %))))
        sum (reduce + factors)
        aliquot-sum (- sum num)]
    (cond
      (= aliquot-sum num) :perfect
      (> aliquot-sum num) :abundant
      (< aliquot-sum num) :deficient)))
(defn is-p [n] 
  (= :perfect (classify n)))
(defn list-perfect [n]
  (filter is-p (range 1 (inc n))))

问题:

  1. 如何构建完全数的惰性序列,以便我可以使用(take n ...)轻松获得列表

  2. 这个代码是习惯的和有效的吗?任何改善吗?

你的算法效率很低,是O(n)

为了快速获胜,您可以立即将范围缩小一半,因为您永远不会有大于您正在测试的数字除以2的因子。

所以改成:

(defn classify [num] 
  (let [factors (->> (range 1 (max 2 (inc (quot num 2))))
  ;; ...
然而

您可以将其更改为O(sqrt n),这是数量级更快。请看下面我的计时。

真正的效率是注意到因子是成对的[x (quot num x)],然后只检查第一个(sqrt num)(或略超过):

(defn perfect? [n]
  (let [r (range 2 (+ 2 (int (Math/sqrt n))))
        s (set (mapcat #(if (zero? (rem n %))
                          [% (quot n %)])
                       r))
        t (reduce + 1 s)]
    (= n t)))

我已经把它分成了单独的计算,所以你可以验证每一个阶段。

范围可以从2..((sqrt n) + 2),并初始化约简为1(它总是一个因子)。

这将问题从O(n)变为O(根号n),因此如果您要检查大数,则会产生巨大的差异。

作为一个例子,这里有一些时间在我的MBP上n的较大值:

         n            "n/2"      "sqrt n"
      33550336       1,172.5ms     2.85ms
    8589869056     274,346.6ms    16.76ms
  137438691328     didn't time    44.27ms

所以使用根版本的速度是第6个完全数的16369倍。详见http://en.wikipedia.org/wiki/List_of_perfect_numbers

编辑:

为什么(int (root n)) + 2?为什么是"x (n x)"?

当你计算一个数字n的因数时,如果你找到一个(比如a),那么n/a也是一个因数(比如b),因为n = a * b

。看看28,第一个相关因子是2,显然28/2 = 14也是一个因子。所以你不需要检查14,你已经知道它是2的因数。

当我们从2开始递增检查数字时,我们偶然发现较大的数字往下:

2 is a factor, 28 / 2 = 14 -> [a, b] = [2, 14]
4 is a factor, 28 / 4 = 7  -> [a, b] = [4,  7]
7 is a factor, 28 / 7 = 4  -> [a, b] = [7,  4] - wait a minute...

这里的[a,b]对是mapcat函数中的[% (quot n %)],例如,当range当前迭代值2时,则%在函数中是2,因此(quot n %)(quot 28 2),即14,因此[% (quot n %)]只是向量[2 14],然后将其平化为214作为值后添加到集合中。之后,当range值为4时,则[% (quot n %)][4 (quot 28 4)],即[4 7],再次被mapcat平展为数字47

因此,我们将每对数字(通过mapcat平坦化)添加到我们的集合中,包括数字1,并最终得到#{1 2 14 4 7},这是28的因数。(实际上,我没有把1放在集合中,因为我不需要,而是从1开始求和,这是相同的效果)。

但是他们在什么时候转身呢?也就是说,我们什么时候知道[7,4]已经作为[4,7]包含在集合中?

显然是当a> b时,因为在寻找最低的数字时,我们总是找到最高的数字,所以我们可以在这里完成检查。

但是这个点是什么?很简单,如果一个完全数是平方数,那么a和b就会相等,即a*a = n,所以a = sqrt(n)。

因此,我们需要检查的a的最大值是大于n的根的整数。

。对于28,sqrt(28) = 5.292,所以我们必须检查6,以确保我们包含了可能是有成对因子的因子的最小数。

我们需要(int (sqrt n)) + 1

我总是在根计算是1.9999999999的情况下这样做…它的四舍五入方式是错误的,所以再加1可以确保消除任何四舍五入错误。

但是在一个范围内,如果你想包含那个数字,你必须再加1(范围降低高的数字,(范围6)=(0 1 2 3 4 5)),因此它要加2的值:1为范围,1确保它在四舍五入的根之上。

虽然,在说了这句话之后,我已经测试了到2305843008139952128的完全数,它可以用+1而不是+2工作,并不是说这是一个巨大的节省。可能是因为在我检查的数字中,没有一个完全数接近完全平方,所以(int (sqrt n))中没有舍入误差。

如果你对完全数感兴趣,我建议你阅读http://britton.disted.camosun.bc.ca/perfect_number/lesson1.html

list-perfect已经懒惰了,因为您使用了过滤器:

(filter pred coll)

返回coll中项的惰性序列(pred item)返回true。药物必须无副作用。

代码是否习惯用语可能是一个意见问题(因此偏离主题),但从我的角度来看,它看起来已经足够好了。

相关内容

  • 没有找到相关文章

最新更新