Clojure 懒惰的序列;“展望未来”;对于Euler问题14项目
我试着用一种懒惰的方式来解决这个问题。不幸的是,我可能正在尝试做一件不可能的事情:创建一个懒惰的序列,它既懒惰,又以某种方式“展望”它尚未计算的值 我为测试正确性而编写的非惰性版本是:Clojure 懒惰的序列;“展望未来”;对于Euler问题14项目,clojure,lazy-evaluation,lazy-sequences,collatz,Clojure,Lazy Evaluation,Lazy Sequences,Collatz,我试着用一种懒惰的方式来解决这个问题。不幸的是,我可能正在尝试做一件不可能的事情:创建一个懒惰的序列,它既懒惰,又以某种方式“展望”它尚未计算的值 我为测试正确性而编写的非惰性版本是: (defn chain-length [num] (loop [len 1 n num] (cond (= n 1) len (odd? n) (recur (inc len) (+ 1 (* 3 n))) true (recur (inc len) (/ n 2))
(defn chain-length [num]
(loop [len 1
n num]
(cond
(= n 1) len
(odd? n) (recur (inc len) (+ 1 (* 3 n)))
true (recur (inc len) (/ n 2)))))
这很有效,但速度很慢。我当然可以记住:
(def memoized-chain
(memoize
(fn [n]
(cond
(= n 1) 1
(odd? n) (+ 1 (memoized-chain (+ 1 (* 3 n))))
true (+ 1 (memoized-chain (/ n 2)))))))
然而,我真正想做的是了解惰性序列的限制,并编写如下函数:
(def lazy-chain
(letfn [(chain [n] (lazy-seq
(cons (if (odd? n)
(+ 1 (nth lazy-chain (dec (+ 1 (* 3 n)))))
(+ 1 (nth lazy-chain (dec (/ n 2)))))
(chain (+ n 1)))))]
(chain 1)))
(def funky-seq
(let [substrate (atom ())]
(letfn [(funk [n] (delay (if (odd? n) n @(nth @substrate (inc n)))))]
(reset! substrate (map funk (range))))
(map deref @substrate)))
user> (take 10 funky-seq)
(1 1 3 3 5 5 7 7 9 9)
从中提取元素将导致n>2的堆栈溢出,如果您考虑为什么需要在n=3时“展望未来”才能知道延迟列表中第十个元素的值,这是可以理解的,因为(+1(*3 n))=10
由于懒惰列表的开销比记忆要小得多,我想知道,通过更延迟的评估或排队,这种事情是否有可能发生?A seq“展望未来”
展望未来的时髦序列示例可能如下所示:
(def lazy-chain
(letfn [(chain [n] (lazy-seq
(cons (if (odd? n)
(+ 1 (nth lazy-chain (dec (+ 1 (* 3 n)))))
(+ 1 (nth lazy-chain (dec (/ n 2)))))
(chain (+ n 1)))))]
(chain 1)))
(def funky-seq
(let [substrate (atom ())]
(letfn [(funk [n] (delay (if (odd? n) n @(nth @substrate (inc n)))))]
(reset! substrate (map funk (range))))
(map deref @substrate)))
user> (take 10 funky-seq)
(1 1 3 3 5 5 7 7 9 9)
(一块饼干送给那些不破坏它而使它更简单的人。:-)
当然,如果确定一个元素的价值可能涉及到一个“未来”元素,而这个元素本身又依赖于另一个元素,需要计算更遥远的元素……,那么灾难是无法避免的
欧拉14
“展望未来”方案的根本问题——问题中的代码试图搁置一旁,这种方法并不能真正解决问题,因为如果你决定从1
开始向上,您需要考虑分支:Collatz链中的10
可能通过应用任一规则(应用于20
的n/2
规则或从3
开始的3n+1
规则)来实现。此外,如果您希望向上构建链,则应反转规则,或者乘以2
,或者减去1
,然后除以3
(在每一步中,应用生成整数的规则,或者如果两者都是整数,则可能两者都是整数)
当然,您可以构建一个树,而不是一个懒惰的列表,但这看起来与问题中的代码草图完全不同,我希望您最终能够记住这件事
以上内容需要注意的是,您可能会猜测哪条“链构建规则”可能从
1
开始生成最长的链,而最终条目保持在给定限制以下。如果是这种情况,您可能应该证明它是正确的,然后直接实现它(通过循环
/重复
从1开始);如果没有证据,你就不能真正声称已经解决了这个问题。下面给出了collatz值的惰性序列。在我的机器上的微基准上,它比记忆解决方案略胜一筹。另一方面,它的递归太深,无法找到100万个collatz链长度,堆栈溢出在第10万到第100万个元素之间的某个位置,但这可以通过一点工作来解决,并且重复出现
(defn collatz [n cache]
(if-let [v (cache n)]
[v cache]
(let [[p cache] (if (odd? n)
(collatz (+ 1 (* 3 n)) cache)
(collatz (/ n 2) cache))]
[(inc p) (assoc cache n (inc p))])))
(def lazy-collatz
(map first (iterate (fn [[v cache n]]
(concat (collatz n cache) [(inc n)]))
[1 {1 1} 2])))
然而,它仍然不是我想要的:没有hashmap的相同功能。考虑到Michal的优秀评论和构建递归树的概念,我想我在这里想要的不是一个惰性序列,而是某种惰性递归数据结构,可能是一棵树。这样的数据流技术存在吗
我最初的想法是从未知值(n)构建“延迟”链,这些延迟会继续递归,直到到达一个已知的数字(如1),然后展开,在递归展开时用实际值填充惰性数据结构。如果我们把一个惰性序列看作是一个惰性链表,那么我想要的是一个惰性的无限长向量或树,它可以使用Collatz规则自动找出它的数据依赖关系。hashmap足以解决这个问题,但在某种意义上,它只是我想要的一个近似值:一个惰性数据流向量,其规则应用于如何填充向量中的元素
额外的挑战:有人能想出一种不用hashmap就能轻松表示这种懒惰树/向量的方法吗?我认为迭代
和花点时间
可能会有所帮助(尽管这段代码没有展望未来):
请注意,记忆几乎肯定会比这种噱头有效得多,因为它只分配实际有用的存储空间。。。如果标准的回忆录方案似乎过于简单,请阅读Meikel Brandmeyer关于推广回忆录
的博客文章,以处理其他各种方案:此外,后续文章可能也很有趣:你们关于回忆录的看法绝对正确,我是kotka.de博客的粉丝。我想我没有很好地表达我的问题,因为我的好奇心真的是关于惰性数据结构,而这个问题只是一个惰性数据结构的例子,它不适合“惰性链表”范例。思想?