是否可以在clojure的循环中变异数据?

时间:2015-10-21 06:06:57

标签: clojure functional-programming

我是Clojure的新手,我正在通过Project Euler挑战来感受这种语言。我在this one,我想通过使用地图将因子映射到所有数字中最大出现次数来解决它,所以要用命令式语言来做,我只需要更新地图在循环的每次迭代中。

我很想在Clojure中做同样的事情:创建一个地图,然后更新它(或者更确切地检索它,添加新值,并将其保存在旧地图中,因为数据是不可变的)对于1-20中的每个数字,然后使用最终地图计算答案。

这感觉不对,好像我没有在功能上思考。我是否遗漏了某些东西,或者在通常情况下必须如何完成事情之间存在某种映射,以及我可以使用哪些功能构造来做同样的事情?

谢谢!

3 个答案:

答案 0 :(得分:3)

clojure俱乐部的第一条规则:你永远不会在clojure俱乐部发生变异!

seriousely,不要改变任何东西,除非它是不可避免的(比如保持应用程序的全局状态)。要保持循环状态,通常只将其作为循环参数之一传递。说到你的任务,例如这里是factors函数的一个例子:

(defn factors [n]
  (loop [n n d 2 f []]
    (cond (== 1 n) f
          (zero? (rem n d)) (recur (/ n d) d (conj f d))
          :else (recur n (inc d) f))))

所以,你只是"积累" f中的因子,并将其传递给下一次迭代。

并且对于其余任务,您应该使用更高阶函数:

(->> (range 2 20)
     (map (comp frequencies factors))
     (apply merge-with max)
     (reduce-kv #(apply * %1 (repeat %3 %2)) 1))

(map (comp frequencies factors))创建地图序列,其中每个地图都是素数因子 这个因子的数量,对于范围内的每个数字:

({2 1} {3 1}           ; 2 3
 {2 2} {5 1}           ; 4 5
 {2 1, 3 1} {7 1}      ; 6 7
 {2 3} {3 2}           ; 8 9
 {2 1, 5 1} {11 1}     ; 10 11
 {2 2, 3 1} {13 1}     ; 12 13
 {2 1, 7 1} {3 1, 5 1} ; 14 15
 {2 4} {17 1}          ; 16 17
 {2 1, 3 2} {19 1})    ; 18 19

(apply merge-with max)合并这些地图,如果键相等则使用最多两个值

{2 4, 3 2, 5 1, 7 1, 11 1, 13 1, 17 1, 19 1}

然后你只需乘以k ^ val

顺便说一句,这个任务有一个更好的解决方案,可以用纸和笔,或一行代码来实现。

答案 1 :(得分:3)

我完全赞同 leetwinski - 你不应该在Clojure中改变变量(除非没有其他方法可以完成这项工作)。

我想要添加到leetwinski's answer的唯一方法是提供更优雅的解决方案:

(defn multiple [numbers]
  (reduce #(let [n (/ %1 %2)]
            ; try to divide accumulator by the next number in input collection
            (if (ratio? n)
                ; multiply accumulator by resulting denominator
                (* %1 (denominator n))
                ; or leave it unchanged if it already evenly divisible
                %1))
          numbers))
(multiple (range 1N 11)) ; => 2520N
(multiple (range 1N 21)) ; => 232792560N

答案 2 :(得分:1)

Clojure loop只是一个let,它可以作为尾递归的一个点:其中,粗略地说,loop形式评估为递归这样打电话,没有做任何事情。 recur调用尾递归到封闭的loop,并通过 goto 执行,而不是使用调用堆栈。

你应该改变循环数据吗?问题不会出现。你不能!您不能更改loop绑定而不是let绑定。 recur使用重新绑定重新进入循环。

loop是惯用的Clojure,是解决许多问题的最简单方法。但是,正如您从其他答案中看到的那样,序列库(mapfilterreduce ...)封装了许多常见的数据/控制模式,这些模式通常表达更多解决方案清楚而简洁。