我试图以这种方式找到一个完整数字列表:
(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))))
问题:
如何构建一个完整数字的懒惰序列,以便我可以使用(take n ...)
轻松获取列表。
这段代码是否惯用且高效?有什么改进吗?
提前致谢。
答案 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
时,14
为4
[% (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)
list-perfect
已经很懒惰了
(过滤器pred coll)
返回coll中项目的延迟序列 (pred item)返回true。 pred必须没有副作用。
代码是否是惯用语可能是一个意见问题(因此偏离主题),但从我的角度看它看起来很好。