欧拉项目 #14 和 Clojure 中的记忆化
作为一名 clojurian 新手,建议我浏览Project Euler 问题作为学习语言的一种方式。这绝对是提高技能和获得信心的好方法。我刚刚完成了对问题#14的回答。它工作得很好,但为了让它有效地运行,我必须实现一些记忆。由于我的代码结构方式,我无法使用预先打包的 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))))
As a neophyte clojurian, it was recommended to me that I go through the Project Euler problems as a way to learn the language. Its definitely a great way to improve your skills and gain confidence. I just finished up my answer to problem #14. It works fine, but to get it running efficiently I had to implement some memoization. I couldn't use the prepackaged memoize
function because of the way my code was structured, and I think it was a good experience to roll my own anyways. My question is if there is a good way to encapsulate my cache within the function itself, or if I have to define an external cache like I have done. Also, any tips to make my code more idiomatic would be appreciated.
(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))))
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是使用普通旧
memoize
的惯用(?)版本。如果您急于使用
recur
,请首先考虑map
或reduce
。他们经常做你想做的事,有时做得更好/更快,因为他们利用了分块的 seqs。(inc x)
类似于(+ 1 x)
,但inc
的速度大约是(+ 1 x)
的两倍。Here's an idiomatic(?) version using plain old
memoize
.If you have an urge to use
recur
, considermap
orreduce
first. They often do what you want, and sometimes do it better/faster, since they take advantage of chunked seqs.(inc x)
is like(+ 1 x)
, butinc
is about twice as fast.您可以在 clojure 中捕获周围环境:
您会看到 mul2 函数仅被调用一次。
因此“缓存”被 clojure 捕获并可用于存储值。
You can capture the surrounding environment in a clojure :
You see the mul2 funciton is only called once.
So the 'cache' is captured by the clojure and can be used to store the values.
由于您希望在所有
chain-length
调用之间共享缓存,因此您可以将chain-length
编写为(let [mem (atom {})] (defn chain-length ...))
这样它就只有chain-length
可见。在这种情况下,由于最长的链足够小,您可以使用简单的递归方法定义
chain-length
,并使用 Clojure 的内置memoize
函数。Since you want the cache to be shared between all invocations of
chain-length
, you would writechain-length
as(let [mem (atom {})] (defn chain-length ...))
so that it would only be visible tochain-length
.In this case, since the longest chain is sufficiently small, you could define
chain-length
using the naive recursive method and use Clojure's builtinmemoize
function on that.