优化一个数的除数

时间:2016-06-15 07:12:12

标签: algorithm optimization clojure

我使用以下代码根据此答案获取给定数字的除数数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

2 个答案:

答案 0 :(得分:2)

轻微的事情:使用intlong代替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)...

即可 对我来说,它的工作速度要快4到5倍

另一个变体(实际上是相同的)是将函数签名中的类型提示更改为^long

问题是^Integer类型提示不会影响您案例中的表现(据我所知)。当你调用对象的某些方法(你没有)时,这种提示只是有助于避免反射开销,并且原始类型提示(仅接受^long^double)实际上将值转换为原始类型。