【问题标题】:How to write this as a recursive Clojure function?如何将其编写为递归 Clojure 函数?
【发布时间】:2017-11-12 10:19:35
【问题描述】:

我将说明我想用 Python 做什么(我想用 Clojure 编写它)。我有这个功能:

def f(n):
    s=0
    for d in range(1,n+1):
        s+=d*(n//d)
    return(s)

这基本上是从 d=1 循环到 n (含),并将 d 的值与 n/d 的下限相加。

在 Clojure 中,我想让它成为一个递归函数。 Python 等价物:

def f(d, n):
    if d == 0: return 0
    else: return d*(n//d) + f(d-1, n)

然后我会使用f(n, n) 调用该函数。

我正在尝试这个:

(defn f
     ([n] (f n n))
     ([d n]
      (if (> d 0)
         (recur (dec d) n)
        0)))

但我不知道到目前为止这是否正确,或者在哪里滑入总和或如何做等等。

【问题讨论】:

    标签: recursion clojure sum


    【解决方案1】:

    如果您查看您的 Clojure f 函数,[d n] arity 重复出现

    • d 递减和
    • n不变

    ...直到d 为零,当它返回0

    如果我们将这个arity 写成一个独特的局部函数,使用letfn,我们可以删除不变的n 参数,从f 参数中提取它:

    (defn f [n]
      (letfn [(g [d]
               (if (> d 0)
                   (recur (dec d))
                   0))]
        (g n)))
    

    这当然会产生错误的答案,总是返回0

    (f 10)
    => 0
    

    但是我们可以看到将总和放在哪里:

    (defn f [n]
      (letfn [(g [d]
               (if (> d 0)
                   (+  (* d (quot n d)) (g (dec d)))
                   0))]
        (g n)))
    

    我们必须将recur 恢复为对g 的显式递归调用,因为它被+ 包围。

    但至少它有效:

    (f 10)
    => 87
    

    在 Clojure 中,我想让它成为一个递归函数。

    不要。我在上面做了只是为了向您展示计算适合的位置。

    显式递归在惯用的 Clojure 中很少见。更好地使用封装其常用模式的函数。我不会重复Carciginate 给出的内容,但是一旦您习惯了线程宏,我想您会发现以下内容清晰简洁:

    (defn f [n]
      (->> (range 1 (inc n))
           (map (fn [d] (* d (quot n d))))
           (reduce +)))
    

    顺便说一句,您的 Python 代码的合理模拟是

      (defn f [n]
        (loop [s 0, d 1]
          (if (> d n)
              s
              (recur (+ s (* d (quot n d))) (inc d)))))
    

    【讨论】:

    • 出于某种原因,我只是不喜欢 thread-last 宏。我总是倾向于把我希望被线程化的参数作为第一个参数,但是如果我必须做一个 map 或 reduce 并且它开始变得混乱。不过在这里很好用。
    • @Carcigenicate thread-last 宏可以很容易地重构为传感器,OP 将来可能会喜欢它。我承认这不是提高性能的方法。
    • 我写 Clojure 已经 2 年多了,还没有接触过传感器,所以我不能为他们说话:/。而且我不应该说我不喜欢 thread-last 宏。我不同意将 coll 作为映射和缩减中的最后一个参数的设计决定。出于几个原因,我认为作为第一个论点会更好。
    【解决方案2】:

    我设法获得了 3 种工作方式。不幸的是,这个算法似乎不适合递归。

    为了获得安全的递归,我不得不引入第三个参数。我只是无法安排它,所以recur 处于尾部位置。我还决定向上计数而不是向下计数。我不认为这里有任何剩余的领域,尽管不幸的是它确实变得很长。

    (defn f3
      ([n] (f3 n 1 0))
      ([n d s]
       (if (> d (inc n))
         s
         (recur n (inc d)
                (+ s (* d (quot n d)))))))
    
    (f3 10)
    

    如果不安全递归是可以的,这可以简化很多。我没有添加多个参数列表,而是决定允许 d 使用 & [d?]] 进行默认设置,然后再进行检查。我倾向于避免添加多个参数列表,因为 par-infer 很难处理使其工作所需的缩进。由于recur 处理 var args 的方式,第一种方法无法实现此技巧。它仅在您不使用recur 或使用recur 时才有效,但只能解构1 个var-arg。

    (defn f2 [n & [d?]]
      (let [d (or d? 1)]
        (if (> d (inc n))
          0
          (+ (f2 n (inc d)) (* d (quot n d))))))
    
    (f2 10)
    

    除非您真的需要递归,否则我只会将其写为映射和归约:

    (defn f1 [n]
      (reduce + 0
        (map #(* % (quot n %)))
          (range 1 (inc n)))))
    
    (f1 10)
    

    这对我来说是最简洁的(不使用线程宏。请参阅缩略图的答案)。

    【讨论】:

      【解决方案3】:

      试试这个:

      (defn f
       ([n] (f n n))
       ([d n]
        (if (> d 0)
           (+ (* d (quot n d)) (recur (dec d) n))
          0)))
      

      【讨论】:

      • 我将其更改为 (defn f ([n] (f n n 0)) ([d n s] (if (> d 0) (recur (dec d) n (+ s (* d (quot n d)))) s))),但这似乎可行(将总和作为参数传递)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 2021-04-19
      相关资源
      最近更新 更多