【问题标题】:How to go about composing core functions, rather then using imperative style?如何编写核心函数,而不是使用命令式风格?
【发布时间】:2014-01-20 16:20:14
【问题描述】:

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

(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

【问题讨论】:

  • Adeel,这对我来说似乎是一个非常笼统的问题,关于一个非常具体的案例。如果您解释了例程应该做什么,这将有助于其他人提供有用的建议。它还可以帮助您详细说明例程应该做什么——不是这段代码详细做什么,而是抽象地说明代码应该完成什么。然后你可以去寻找有助于做到这一点的代码和函数。或者您可以询问有关如何开始编写可以做到这一点的代码的问题。
  • @Mars:谢谢你的建议。我已对其进行了更新,以包含简要说明和输出。
  • 能否贴出原python代码供参考?
  • @DominicKexel: 贴出原Python函数。

标签: performance clojure functional-programming lisp idioms


【解决方案1】:

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"

【讨论】:

  • 谢谢,奥米尔。我一定会试试这个——现在没有环境。顺便说一句,您建议的优化也在我的脑海中,但幸运且令人惊讶的是,我遇到了这个数字60085147514 和主要因素(2 7 17 23 10976461),它的最后一个明显大于60085147514 的平方根。这意味着,对于一个数字,为​​了成为一个复合数,必须在该数字的平方根之下至少有一个素数。但这并不意味着不会有更大的素数除数。
  • 不,当然。素数是一个简单的例子。但是,如果手边的除数大于剩余商的平方根,你肯定知道这个商是素数,因为你之前除以所有较小的素因数(并且没有一个更大的素数会除它)。
  • 哦,我明白你的意思了。确实非常好听。
【解决方案2】:

并非每个循环都干净利落地展开为优雅的“函数式”分解。

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

更一般地说,如果您希望扩展您对函数式习语的理解,Bedra 和 Halloway 的“Programming Clojure”(第 90-95 页)使用loop 对不同版本的斐波那契数列进行了出色的比较,惰性序列和优雅的“功能”版本。 Chouser 和 Fogus 的“Joy of Clojure”(MEAP 版本)也有很好的函数组合部分。

【讨论】:

  • 感谢您的建议,JohnJ。我有“Programming Clojure”,但我觉得我开始修补 Clojure 有点太早了。
猜你喜欢
  • 2022-11-10
  • 2011-03-20
  • 2022-11-30
  • 1970-01-01
  • 2016-01-11
  • 2022-07-11
  • 2016-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多