作为一个新手clojurian,recommended to me我通过Project Euler问题作为学习语言的一种方式。它绝对是提高技能和获得信心的好方法。我刚刚完成了对problem #14的回答。它工作正常,但要让它有效运行,我必须实现一些memoization。由于我的代码的结构方式,我无法使用预先打包的memoize
函数,而且我认为无论如何都是一个很好的经验。我的问题是,是否有一种很好的方法将我的缓存封装在函数本身中,或者如果我必须像我所做的那样定义一个外部缓存。此外,任何使我的代码更惯用的提示都将受到赞赏。
(use 'clojure.test)
(def mem (atom {}))
(with-test
(defn chain-length
([x] (chain-length x x 0))
([start-val x c]
(if-let [e (last(find @mem x))]
(let [ret (+ c e)]
(swap! mem assoc start-val ret)
ret)
(if (<= x 1)
(let [ret (+ c 1)]
(swap! mem assoc start-val ret)
ret)
(if (even? x)
(recur start-val (/ x 2) (+ c 1))
(recur start-val (+ 1 (* x 3)) (+ c 1)))))))
(is (= 10 (chain-length 13))))
(with-test
(defn longest-chain
([] (longest-chain 2 0 0))
([c max start-num]
(if (>= c 1000000)
start-num
(let [l (chain-length c)]
(if (> l max)
(recur (+ 1 c) l c)
(recur (+ 1 c) max start-num))))))
(is (= 837799 (longest-chain))))
答案 0 :(得分:3)
由于您希望在chain-length
的所有调用之间共享缓存,因此您应将chain-length
写为(let [mem (atom {})] (defn chain-length ...))
,以便只有chain-length
可见。< / p>
在这种情况下,由于最长链足够小,您可以使用朴素递归方法定义chain-length
并使用Clojure的内置memoize
函数。
答案 1 :(得分:2)
这是使用普通旧版memoize
的惯用(?)版本。
(def chain-length
(memoize
(fn [n]
(cond
(== n 1) 1
(even? n) (inc (chain-length (/ n 2)))
:else (inc (chain-length (inc (* 3 n))))))))
(defn longest-chain [start end]
(reduce (fn [x y]
(if (> (second x) (second y)) x y))
(for [n (range start (inc end))]
[n (chain-length n)])))
如果您有使用recur
的冲动,请先考虑map
或reduce
。他们经常做你想做的事,有时做得更好/更快,因为他们利用了chunked seqs。
(inc x)
与(+ 1 x)
类似,但inc
大约快两倍。
答案 2 :(得分:1)
您可以在clojure中捕获周围环境:
(defn my-memoize [f]
(let [cache (atom {})]
(fn [x]
(let [cy (get @cache x)]
(if (nil? cy)
(let [fx (f x)]
(reset! cache (assoc @cache x fx)) fx) cy)))))
(defn mul2 [x] (do (print "Hello") (* 2 x)))
(def mmul2 (my-memoize mul2))
user=> (mmul2 2)
Hello4
user=> (mmul2 2)
4
你看到mul2功能只被调用一次。
因此,“缓存”由clojure捕获,可用于存储值。