我使用以下代码根据此答案获取给定数字的除数数https://stackoverflow.com/a/110365/2610955。我计算一个素数因子重复的次数,然后递增它们中的每一个并从中产生一个产品。但算法似乎太慢了。有没有办法优化程序。我尝试了类型提示但它们没有任何用处。算法有什么问题,或者我在这里缺少任何优化?
(defn prime-factors
[^Integer n]
(loop [num n
divisors {}
limit (-> n Math/sqrt Math/ceil Math/round inc)
init 2]
(cond
(or (>= init limit) (zero? num)) divisors
(zero? (rem num init)) (recur (quot num init) (update divisors init #(if (nil? %) 1 (inc %))) limit init)
:else (recur num divisors limit (inc init)))))
(prime-factors 6)
(set! *unchecked-math* true)
(dotimes [_ 10] (->> (reduce *' (range 30 40))
prime-factors
vals
(map inc)
(reduce *' 1)
time))
编辑:删除最终输出中不需要的rem
答案 0 :(得分:2)
轻微的事情:使用int
或long
代替Integer
,如@leetwinski所述。
主要原因:
似乎你从2循环到sqrt(n)
,这将循环不必要的数字。
看看下面的代码:(这只是一个肮脏的补丁)
(defn prime-factors
[n]
(loop [num n
divisors {}
limit (-> n Math/sqrt int)
init 2]
(cond
(or (>= init limit) (zero? num)) divisors
(zero? (rem num init)) (recur (quot num init) (update divisors init #(if (nil? %) 1 (inc %))) limit init)
:else (recur num divisors limit (if (= 2 init ) (inc init) (+ 2 init))))))
我只需将(inc init)
替换为(if (= 2 init ) (inc init) (+ 2 init))
。这将循环超过2和奇数。如果运行它,您会注意到执行时间几乎减少了原始版本的一半。因为它跳过偶数(2除外)。
如果仅循环素数,它将比这快得多。您可以从clojure.contrib.lazy-seqs/primes
获取素数序列。虽然这个contrib命名空间已经弃用,但你仍然可以使用它。
这是我的方法:
(ns util
(:require [clojure.contrib.lazy-seqs :refer (primes)]))
(defn factorize
"Returns a sequence of pairs of prime factor and its exponent."
[n]
(loop [n n, ps primes, acc []]
(let [p (first ps)]
(if (<= p n)
(if (= 0 (mod n p))
(recur (quot n p) ps (conj acc p))
(recur n (rest ps) acc))
(->> (group-by identity acc)
(map (fn [[k v]] [k (count v)])))))))
然后您可以像这样使用此功能:
(dotimes [_ 10] (->> (reduce *' (range 30 40))
factorize
(map second)
(map inc)
(reduce *' 1)
time))
答案 1 :(得分:1)
您可以在循环中使用原始int
来提高性能。只需将(loop [num n...
替换为(loop [num (int n)...
另一个变体(实际上是相同的)是将函数签名中的类型提示更改为^long
。
问题是^Integer
类型提示不会影响您案例中的表现(据我所知)。当你调用对象的某些方法(你没有)时,这种提示只是有助于避免反射开销,并且原始类型提示(仅接受^long
和^double
)实际上将值转换为原始类型。