我是Clojure的新手,我正在通过Project Euler挑战来感受这种语言。我在this one,我想通过使用地图将因子映射到所有数字中最大出现次数来解决它,所以要用命令式语言来做,我只需要更新地图在循环的每次迭代中。
我很想在Clojure中做同样的事情:创建一个地图,然后更新它(或者更确切地检索它,添加新值,并将其保存在旧地图中,因为数据是不可变的)对于1-20中的每个数字,然后使用最终地图计算答案。
这感觉不对,好像我没有在功能上思考。我是否遗漏了某些东西,或者在通常情况下必须如何完成事情之间存在某种映射,以及我可以使用哪些功能构造来做同样的事情?
谢谢!
答案 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,是解决许多问题的最简单方法。但是,正如您从其他答案中看到的那样,序列库(map
,filter
,reduce
...)封装了许多常见的数据/控制模式,这些模式通常表达更多解决方案清楚而简洁。