我正在学习Clojure。我对函数式编程非常陌生,想知道我的代码是否散发出气味,或者我的方法是否会对性能产生影响。

; Returns the the given sequence with the given item appended to it.
(defn snoc [xs x] (concat xs [x]))

; Returns the Fibonacci sequence up to the highest number less than max.
(defn fib [max] 
  (loop [a 1, b 1, acc [1]] 
    (if (> b max) 
      acc
      (recur b (+ a b) (snoc acc b)))))

; Project Euler Problem 2: Attempt A
(defn pe2a []
  (reduce +
    (filter even? (fib 4000000))))


作为参考:


Fibonacci
序列中的每个新术语都是通过将
前两个术语相加而生成的。以1
和2开头,前10个项将是:

1、2、3、5、8、13、21、34、55、89,...

通过考虑
值不超过
的斐波那契数列中的项
,求和
偶值项的总和。


评论

您可以直接生成偶数项,而不是生成所有项并丢弃奇数项。直接方法花费更少的时间来生成多达400万个序列。

#1 楼

(defn snoc [xs x] (concat xs [x]))


有一个原因在clojure中没有默认定义snoc:由于在单链表的末尾附加需要O(n)的时间,因此这实际上很昂贵。使用功能语言递归地构建非惰性列表时,通常会以错误的方式构建列表(使用cons而不是snoc),然后在最后将其反转以避免这种开销。

但是在这种情况下,实际上有一个更好的方法:通过使用惰性序列而不是严格的列表,我们可以避免需要loop / recur并节省构建列表的成本。我们还可以通过先创建一个包含所有斐波那契数字的惰性序列,然后使用take-while接受小于给定最大值的延迟序列,来将创建斐波那契数字的逻辑与决定所需数量的逻辑分开。这将导致以下代码:

;; A lazy sequence containing all fibonacci numbers
(def fibs
  (letfn
    [(fibsrec [a b]
      (lazy-seq (cons a (fibsrec b (+ a b)))))]
    (fibsrec 1 1)))

;; A function which returns all fibonacci numbers which are less than max
(defn fibs-until [max]
  (take-while #(<= % max) fibs))


评论


\ $ \ begingroup \ $
我想我已经很好地分解了您的代码。可以肯定的是,%的作用是什么?据我所知,它代表隐式传递给匿名函数的参数。
\ $ \ endgroup \ $
–杰里米·海勒(Jeremy Heiler)
2011年1月27日20:12



\ $ \ begingroup \ $
@Jeremy:是的。 #(<=%max)只是写(fn [x](<= x max))的一种简短方法,即,一个函数接受一个参数,然后检查该参数是否小于或等于max。
\ $ \ endgroup \ $
–sepp2k
2011年1月27日20:18

#2 楼

我不确定这是否属于此处的注释,但我也使用clojure构建了解决方案。斐波那契数是按照Halloway的“ Clojure编程” p中的无穷序列生成的。 136.然后,我使用列表推导进行求和

;;Programming in Clojure p. 136

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

(defn proj-euler2 [] 
  (reduce + (for [x (fibo) :when (even? x) :while (< x 4000000)] x)))


#3 楼

而不是编写snoc,只需使用conj。

(conj [1 2] 3)


返回

[1 2 3]


请注意,这仅适用于向量。

(conj (list 1 2) 3)


返回

(3 1 2)


最后,如果涉及性能,则可以使用瞬态向量并将其保留在最后可能的时刻。

PS我喜欢戴夫的解决方案。