【问题标题】:Cleaning up Clojure function清理 Clojure 函数
【发布时间】:2011-09-13 02:48:48
【问题描述】:

来自命令式编程语言,我试图围绕 Clojure 进行研究,希望将其用于多线程功能。
4Clojure 的问题之一是编写一个函数,该函数生成一个长度为 N 的斐波那契数列表,对于 N > 1。我写了一个函数,但鉴于我的背景有限,我想了解一下这是否是最好的 Clojure 做事方式。代码如下:

(fn fib [x] (cond 
               (= x 2) '(1 1)
            :else   (reverse (conj (reverse (fib (dec x))) (+ (last (fib (dec x))) (-> (fib (dec x)) reverse rest first))))
          ))

【问题讨论】:

标签: clojure refactoring fibonacci


【解决方案1】:

最惯用的“函数式”方法可能是创建一个无限的惰性斐波那契数序列,然后提取前 n 个值,即:

(take n some-infinite-fibonacci-sequence)

以下链接有一些非常有趣的方式来生成斐波那契序列:

http://en.wikibooks.org/wiki/Clojure_Programming/Examples/Lazy_Fibonacci

最后,这是另一个值得考虑的有趣实现:

 (defn fib [n]
   (let [next-fib-pair (fn [[a b]] [b (+ a b)])
         fib-pairs (iterate next-fib-pair [1 1])
         all-fibs (map first fib-pairs)]
     (take n all-fibs)))


 (fib 6)
 => (1 1 2 3 5 8)

它没有尽可能简洁,但很好地展示了使用 Clojure 的解构、惰性序列和高阶函数来解决问题。

【讨论】:

    【解决方案2】:

    这是我非常喜欢的一个斐波那契版本(我从 clojure wikibook 中获取了实现:http://en.wikibooks.org/wiki/Clojure_Programming

     (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq)))
    

    它的工作原理如下:假设您已经拥有无限的斐波那契数列。如果你取序列的尾部并将其逐元素添加到原始序列中,你将得到斐波那契序列的(尾部的尾部)

    0 1 1 2 3 5 8 ...
    1 1 2 3 5 8 ...
    -----------------
    1 2 3 5 8 13 ... 
    

    因此您可以使用它来计算序列。您需要两个初始元素 [0 1] (或 [1 1] 取决于您开始序列的位置),然后您只需映射添加元素的两个序列。请注意,这里需要惰性序列。

    我认为这是最优雅且(至少对我而言)令人费解的实现。

    编辑:fib函数是

     (defn fib [n] (nth fib-seq n)) 
    

    【讨论】:

      【解决方案3】:

      这是一种让您对惰性序列有所了解的方法,尽管它肯定不是计算斐波那契序列的最佳方法。

      鉴于斐波那契数列的定义,我们可以看到它是通过重复将相同的规则应用于'(1 1) 的基本情况而构建的。 Clojure 函数 iterate 听起来很适合这个:

      user> (doc iterate)
      -------------------------
      clojure.core/iterate
      ([f x])
        Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects
      

      因此,对于我们的函数,我们需要获取我们目前计算的值,将最近的两个值相加,并返回新值和所有旧值的列表。

      (fn [[x y & _ :as all]] (cons (+ x y) all))
      

      这里的参数列表只是意味着xy将绑定到作为函数参数传递的列表中的前两个值,包含前两个参数之后的所有参数的列表将绑定到_ ,并且作为参数传递给函数的原始列表可以通过all引用。

      现在,iterate 将返回一个无限的中间值序列,所以对于我们的例子,我们希望将它包装在一个只返回我们感兴趣的值的东西中;惰性求值将停止整个无限序列的求值。

      (defn fib [n]
         (nth (iterate (fn [[x y & _ :as all]] (cons (+ x y) all)) '(1 1)) (- n 2)))
      

      还要注意,这会以与您的实现相反的顺序返回结果;当然,用reverse 解决这个问题很简单。

      编辑:或者实际上,正如 amalloy 所说,通过使用向量:

      (defn fib [n]
         (nth (iterate (fn [all]
                         (conj all (->> all (take-last 2) (apply +)))) [1 1])
              (- n 2)))
      

      【讨论】:

      • Clojure 不需要重用 reverse 的 Common Lisp 反模式,当你最终完成构建它时,它的 cons 链,因为它的向量向右而不是向左增长。
      • 感谢您的详细回复。创建惰性列表和使用“nth”函数仅根据需要进行评估的绝佳示例。
      【解决方案4】:

      在 Stu Halloway 的 Programming Clojure 中查看 Christophe Grand 的 Fibonacci 解决方案。这是我见过的最优雅的解决方案。

      (defn fibo [] (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
      (take 10 (fibo))
      

      另见

      How can I generate the Fibonacci sequence using Clojure?

      【讨论】:

        猜你喜欢
        • 2014-09-06
        • 2015-07-04
        • 2023-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-07
        • 2020-04-09
        • 1970-01-01
        相关资源
        最近更新 更多