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博客的粉丝。我想我没有很好地表达我的问题,因为我的好奇心真的是关于惰性数据结构,而这个问题只是一个惰性数据结构的例子,它不适合“惰性链表”范例。思想?