如何在Clojure中构建一个完美数字的懒惰序列?

时间:2015-05-19 10:09:38

标签: 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. 这段代码是否惯用且高效?有什么改进吗?

  3. 提前致谢。

2 个答案:

答案 0 :(得分: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(sqrt 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

所以使用root版本对于第6个完美数字来说快16,369倍。有关详细信息,请参阅http://en.wikipedia.org/wiki/List_of_perfect_numbers

编辑:

为什么(int(root n))+ 2?为什么`[x(“n x)]?

当您计算出数字n的因子时,如果您找到一个因子(例如,a),那么n/a也是一个因素(称之为b),因为{ {1}}

e.g。看28,第一个相关因素是2,显然28/2 = 14也是一个因素。所以你不需要检查14,你已经知道它是因为2是这个因素。

当我们从2向上逐步检查数字时,我们偶然会发现更高的数字下降:

n = a * b

这里的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... 对是mapcat函数中的[a,b],例如当范围当前正在迭代值[% (quot n %)]时,2在函数内部为%,因此2(quot n %)为14,因此{{ 1}}只是向量(quot 28 2),然后在被展平为[% (quot n %)][2 14]作为值后添加到集合中。稍后,当范围值为2时,144 [% (quot n %)],并且mapcat再次将其展平为数字[4 (quot 28 4)]和{{ 1}}。

所以我们将每对数字(通过mapcat展平)添加到我们的集合中,包括数字1,最后得到[4 7],这是28的因子。(实际上,我不会这样做在我不需要的时候把1放在集合中,而不是在1处开始求和,这是相同的效果。)

但他们在什么时候转过身来?即我们什么时候才知道4已经包含在7中?

明确表示#{1 2 14 4 7}&gt; [7,4],因为在查找最小数字时我们总是找到最高数字,所以我们可以在此时完成检查。

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

因此,我们需要检查的[4,7]的最高值是大于a的根的整数。

e.g。对于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,它可以用+而不是+2,而不是它是一个巨大的节省。可能是因为我检查过的非完美数字接近完美正方形,所以b中没有舍入错误。

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

答案 1 :(得分:1)

由于您使用了filter

list-perfect已经很懒惰了

  

(过滤器pred coll)

     

返回coll中项目的延迟序列   (pred item)返回true。 pred必须没有副作用。

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