如何构建核心功能,而不是使用命令式?

时间:2014-01-03 03:41:37

标签: performance clojure functional-programming lisp idiomatic

我已将此代码(下面的代码段)从Python翻译为Clojure。我在这里用Clojure的while替换了Python的loop-recur构造。但这看起来并不惯用。

(loop [d 2 [n & more] (list 256)]
      (if (> n 1)
        (recur (inc d)
               (loop [x n sublist more]
                 (if (= (rem x d) 0)
                   (recur (/ x d) (conj sublist d))
                   (conj sublist x))))
        (sort more)))

这个例程给了我(3 3 31),这是279的主要因素。对于256,它提供(2 2 2 2 2 2 2 2),表示2^8

此外,对于较大的值,它会表现更差,例如987654123987546而不是279;而Python的对手就像魅力一样。

如何开始编写核心功能,而不是按原样翻译命令式代码?具体来说,如何改善这一点?

感谢。

[编辑]

这是我在上面提到的python代码,

def prime_factors(n):
    factors = []
    d = 2
    while n > 1:
        while n % d == 0:
            factors.append(d)
            n /= d
        d = d + 1
    return factors

2 个答案:

答案 0 :(得分:2)

并非每个循环都能完全展开为优雅的“功能”分解。

@edbond建议的Rosetta Code解决方案非常简洁明了;我会说这是惯用的,因为没有明显的“功能”解决方案。我的机器上的解决方案运行速度明显快于987654123987546的Python版本。

更一般地说,如果你想扩展对功能习语的理解,Bedra和Halloway的“Programming Clojure”(pp.90-95)使用{{1}对斐波那契序列的不同版本进行了极好的比较。 },lazy seqs和优雅的“功能”版本。 Chouser和Fogus的“Clojure的喜悦”(MEAP版本)也有很好的功能组合部分。

答案 1 :(得分:2)

Clojure中Python代码的直接翻译将是:

(defn prime-factors [n]
  (let [n       (atom n)  ;; The Python code makes use of mutability which
        factors (atom []) ;; isn't idiomatic in Clojure, but can be emulated
        d       (atom 2)] ;; using atoms
    (loop []
      (when (< 1 @n)
        (loop []
          (when (== (rem @n @d) 0)
            (swap! factors conj @d)
            (swap! n quot @d)
            (recur)))
        (swap! d inc)
        (recur)))
    @factors))

(prime-factors 279)                    ;; => [3 3 31]
(prime-factors 987654123987546)        ;; => [2 3 41 14389 279022459]
(time (prime-factors 987654123987546)) ;; "Elapsed time: 13993.984 msecs"
                                       ;; same performance on my machine
                                       ;; as the Rosetta Code solution

您可以改进此代码,使其更具惯用性:

  • 从嵌套循环到单个循环:
    (loop []
      (cond
        (<= @n 1)            @factors
        (not= (rem @n @d) 0) (do (swap! d inc)
                                 (recur))
        :else                (do (swap! factors conj @d)
                                 (swap! n quot @d)
                                 (recur))))))
  • 摆脱原子:
    (defn prime-factors [n]
      (loop [n       n
             factors []
             d       2]
        (cond
          (<= n 1)           factors
          (not= (rem n d) 0) (recur n factors (inc d))
          :else              (recur (quot n d) (conj factors d) d))))
  • == 0替换为zero?
          (not (zero? (rem n d))) (recur n factors (inc d))

你也可以彻底检修它以制作它的懒惰版本:

(defn prime-factors [n]
  ((fn step [n d]
     (lazy-seq 
       (when (< 1 n)
         (cond
           (zero? (rem n d)) (cons d (step (quot n d) d))
           :else             (recur n (inc d)))))
   n 2))

我计划在这里有一个关于优化的部分,但我不是专家。我唯一可以说的是,当d大于n的平方根时,你可以通过中断循环来简化这段代码:

    (defn prime-factors [n]
      (if (< 1 n)
        (loop [n       n
               factors []
               d       2]
          (let [q (quot n d)]
            (cond
              (< q d)           (conj factors n)
              (zero? (rem n d)) (recur q (conj factors d) d)
              :else             (recur n factors (inc d)))))
        []))

    (time (prime-factors 987654123987546)) ;; "Elapsed time: 7.124 msecs"