Clojure - 总结了一堆数字

时间:2014-07-04 14:26:46

标签: clojure lisp

嘿我正在做一个项目欧拉问题,我想要总结1000以下3或5倍的数字。

但是作为一个clojure菜鸟,我的代码只是保持归零......我不确定为什么。

(defn sum-of-multiples [max]
  (let [result (atom 0)]
    (for [i (range max)] 
      (if (or (= (rem i 3) 0) (= (rem i 5) 0))
        (swap! result (+ @result i)))    
      )
    @result))

(sum-of-multiples 1000)

(swap! result (+ @result i)))也让我感到烦恼..在C#中我可以做result += i,但我猜猜在Clojure中必须有更好的方法吗?

3 个答案:

答案 0 :(得分:14)

在Clojure中 - 在函数式编程中 - 我们避免了赋值,因为它破坏了状态历史并使编写并发程序变得更加困难。事实上,Clojure甚至不支持任务。 atom是一个线程安全的引用类型。

函数式编程的另一个共同特点是我们尝试将问题解决为一系列数据转换。在您的情况下,您会出现一些数据,0到1000之间的数字列表,并且您需要获取与谓词匹配的所有数字的总和。这当然可以通过应用数据转换并完全消除分配的需要来完成。其中一个实现就是:

(->> (range 1000)
     (filter #(or (= (rem % 3) 0) (= (rem % 5) 0)))
     (reduce +))

请理解您编写的功能不被视为惯用代码。话虽如此,为了学习,它可以像这样工作:

(defn sum-of-multiples [max]
  (let [result (atom 0)]
    (doseq [i (range max)] 
      (if (or (= (rem i 3) 0) (= (rem i 5) 0))
        (swap! result #(+ % i)))    
      )
    @result))

(sum-of-multiples 1000)

for会返回一个懒惰的序列,但由于您只对swap!引起的副作用感兴趣,因此您需要使用doseq强制序列。另一个问题是swap!的第二个参数是一个函数,因此您不需要再次解除result

答案 1 :(得分:5)

for是一个返回惰性序列的列表解析,你必须遍历它才能使你的代码工作:

(defn sum-of-multiples [max]
  (let [result (atom 0)]
    (dorun
      (for [i (range max)] 
        (if (or (= (rem i 3) 0) (= (rem i 5) 0))
          (swap! result + i))))
    @result))

使用for的等效,更惯用的实现:

(defn sum-of-multiples [max]
  (reduce +
    (for [i (range max)
          :when (or (zero? (rem i 3))
                    (zero? (rem i 5)))] 
      i)))

答案 2 :(得分:2)

其他答案是我在评论中提到的很好的例子。为了完整起见,这是一个使用loop/recur的解决方案,因此对于仍然不熟悉filter,map或reduce等概念的人来说可能更容易理解。它也恰好快30-40%,而不是在这种情况下真的很重要。

(defn sum-of-multiples [max]
  (loop [i 0
         sum 0]
    (if (> max i)
       (recur (inc i)
              (if (or (zero? (rem i 3)) (zero? (rem i 5)))
                (+ sum  i)
                sum))
        sum)))