Clojure序列中修剪循环的简便方法

时间:2018-08-08 20:21:48

标签: clojure

我正在尝试编写一个惰性seq以为给定的输入int生成Collatz sequence

我喜欢这个函数,因为它很干净地映射到数学定义:

(defn collatz
  "Returns a lazy seq of the Collatz sequence starting at n and ending at 1 (if
  ever)."
  [n]
  (letfn [(next-term [x]
            (if (even? x)
              (/ x 2)
              (inc (* 3 x))))]
    (iterate next-term n)))

问题是由于Collat​​z序列的行为,这会产生无限个序列:

(take 10 (collatz 5))
  => (5 16 8 4 2 1 4 2 1 4)

我可以通过添加(take-while #(not= 1 %) ...)轻松地删除循环,但是1 序列的一部分。我想过的其他所有方法都可能使之后的周期变得很丑陋,并且模糊了Collat​​z序列的数学核心。

(我曾考虑过将可见的值存储在一个原子中,并在take-while谓词中使用它,或者只是将一个标志存储在一个原子中,以达到类似的效果。但是我觉得有一些更好,更漂亮的方法,在这里做我想做的事情时比较不打扰。)

所以我的问题是:检测和修整无限序列中的周期的干净方法是什么?或者,我是否可以通过某种方式(可能使用for)来生成我的懒惰序列(当它到达1(含)时会自动修剪?

2 个答案:

答案 0 :(得分:6)

以下内容看起来像是该定义的大致文字转换,并提供了所需的结果:

Require-Bundle

基本上,您可以使用Import-Package作为停止序列的值,从而保留最后的1。

答案 1 :(得分:1)

您还可以使用另一种方法,即递归惰性序列生成。对于此类任务,这是很常见的,不会破坏惰性序列抽象,并且避免了中间序列的创建:

(defn collatz [n]
  (if (== n 1)
    (list 1)
    (lazy-seq (cons n (collatz (if (even? n)
                                 (/ n 2)
                                 (inc (* 3 n))))))))

user> (collatz 12)
;;=> (12 6 3 10 5 16 8 4 2 1)