【问题标题】:How to return a lazy sequence from a loop recur with a conditional in Clojure?如何在 Clojure 中使用条件从循环中返回惰性序列?
【发布时间】:2017-09-26 20:09:13
【问题描述】:

对于 Clojure 和一般编程来说仍然很新,所以请原谅这个愚蠢的问题。

问题是:

求 n 和 k,使得直到 n(不包括)的数字之和等于从 n+1 到 k(包括)的数字之和。

我的解决方案(效果很好)是定义以下函数:

(defn addd [x] (/ (* x (+ x 1)) 2))
(defn sum-to-n [n] (addd(- n 1)))
(defn sum-to-k [n=1 k=4] (- (addd k) (addd n)))
(defn is-right[n k]
  (= (addd (- n 1)) (sum-to-k n k)))

然后运行以下循环:

 (loop [n 1 k 2]
  (cond 
   (is-right n k) [n k]
   (> (sum-to-k n k) (sum-to-n n) )(recur (inc n) k)
   :else (recur n (inc k))))

这只会返回一个答案,但如果我手动设置 n 和 k 我可以获得不同的值。但是,我想定义一个返回所有值的惰性序列的函数,以便:

(= [6 8] (take 1 make-seq))

如何尽可能高效地做到这一点?我尝试了各种方法,但运气不佳。

谢谢

:编辑:

我想我想出了一个更好的方法,但它返回“让应该是一个向量”。 Clojure 文档没有多大帮助...

这是新代码:

(defn calc-n [n k]
(inc (+ (* 2 k) (* 3 n))))

(defn calc-k [n k]
(inc (+ (* 3 k)(* 4 n))))

(defn f
   (let [n 4 k 6]
      (recur (calc-n n k) (calc-k n k))))

(take 4 (f))

【问题讨论】:

  • 在您的编辑中,您缺少f 的参数向量。

标签: clojure functional-programming


【解决方案1】:

是的,您可以创建一个惰性序列,以便下一次迭代将采用上一次迭代的结果。这是我的建议:

(defn cal [n k]
   (loop [n n k k]
     (cond
       (is-right n k) [n k]
       (> (sum-to-k n k) (sum-to-n n) )(recur (inc n) k)
       :else (recur n (inc k)))))

(defn make-seq [n k]
  (if-let [[n1 k1] (cal n k)]
      (cons [n1 k1] 
            (lazy-seq (make-seq (inc n1) (inc k1))))))

 (take 5 (make-seq 1 2)) 
 ;;=>  ([6 8] [35 49] [204 288] [1189 1681] [6930 9800])

【讨论】:

  • 你为什么使用if-let?似乎只需要一个简单的let
  • 如果cal 的某些未来实现可以确定再也找不到答案,if-let 允许序列终止。例如,与take 的定义相比,其核心类似于(defn take [n xs] (lazy-seq (if (zero? n) (), (if-let [coll (seq xs)] (cons (first coll) (take (dec n (rest coll))))))))
  • 是的,就像 amalloy 说的,需要 if-let 来终止惰性序列
【解决方案2】:

只需使用iterate 生成惰性候选序列,然后过滤它们可能就是您需要的:

(def pairs
  (->> [1 2]
       (iterate (fn [[n k]]
                  (if (< (sum-to-n n) (sum-n-to-k n k))
                    [(inc n) k]
                    [n (inc k)])))
       (filter (partial apply is-right))))

user> (take 5 pairs)
;;=> ([6 8] [35 49] [204 288] [1189 1681] [6930 9800])

从语义上讲,它就像手动生成惰性序列一样,应该同样有效,但这个可能更惯用

【讨论】:

    【解决方案3】:

    如果您不想“自己动手”,这里有一个替代解决方案。我还通过重命名/重新格式化对算法进行了一些清理。

    主要区别在于您将循环递归视为t/lazy-gen 表单内的无限循环。当您找到要保留的值时,您可以使用 t/yield 表达式来创建输出的惰性序列。此结构是 生成器函数 的 Clojure 版本,就像在 Python 中一样。

    (ns tst.demo.core
      (:use tupelo.test )
      (:require [tupelo.core :as t] ))
    
    (defn integrate-to [x]
      (/ (* x (+ x 1)) 2))
    (defn sum-to-n [n]
      (integrate-to (- n 1)))
    (defn sum-n-to-k [n k]
      (- (integrate-to k) (integrate-to n)))
    (defn sums-match[n k]
      (= (sum-to-n n) (sum-n-to-k n k)))
    
    (defn recur-gen []
      (t/lazy-gen
        (loop [n 1 k 2]
          (when (sums-match n k)
            (t/yield [n k]))
          (if (< (sum-to-n n) (sum-n-to-k n k))
            (recur (inc n) k)
            (recur n (inc k))))))
    

    结果:

    -------------------------------
       Clojure 1.10.1    Java 13
    -------------------------------
    
    (take 5 (recur-gen)) => ([6 8] [35 49] [204 288] [1189 1681] [6930 9800])
    

    您可以找到所有详细信息in the Tupelo Library

    【讨论】:

      【解决方案4】:

      这第一个函数可能有一个更好的数学名称,但我不太了解数学。我会使用inc(增量)而不是(+ ,,, 1),但这只是个人喜好。

      (defn addd [x]
        (/ (* x (inc x)) 2))
      

      这里我稍微清理一下间距,使用dec(减量)函数。

      (defn sum-to-n [n]
        (addd (dec n)))
      
      (defn sum-n-to-k [n k]
        (- (addd k) (addd n)))
      

      在某些语言中,谓词、返回布尔值的函数、 名称类似于 is-oddis-whatever。在clojure中,它们通常是 称为odd?whatever?。 问号不是语法,它只是名称的一部分。

      (defn matching-sums? [n k]
        (= (addd (dec n)) (sum-n-to-k n k)))
      

      循环特殊形式有点像匿名函数 重复跳回。如果没有循环形式,recur 跳回 到封闭函数。 另外,不知道该叫什么,所以我就叫它f

      (defn f [n k]
        (cond
          (matching-sums? n k) [n k]
          (> (sum-n-to-k n k) (sum-to-n n)) (recur (inc n) k)
          :else (recur n (inc k))))
      
      (comment
      
        (f 1 2) ;=> [6 8]
      
        (f 7 9) ;=> [35 49]
      
        )
      

      现在,针对您的实际问题。如何制作一个惰性序列。您可以使用 lazy-seq 宏,就像在 minhtuannguyen 的回答中一样,但有一种更简单、更高级别的方法。使用iterate 函数。 iterate 接受一个函数和一个值,并返回一个无限序列的值,然后用该值调用函数,然后在 那个 值上调用函数,等等。

      (defn make-seq [init]
        (iterate (fn [n-and-k]
                   (let [n (first n-and-k)
                         k (second n-and-k)]
                     (f (inc n) (inc k))))
                 init))
      
      (comment
      
        (take 4 (make-seq [1 2])) ;=> ([1 2] [6 8] [35 49] [204 288])
        )
      

      这可以通过在匿名函数的参数向量中使用解构来简化。

      (defn make-seq [init]
        (iterate (fn [[n k]]
                   (f (inc n) (inc k)))
                 init))
      

      编辑: 关于f中的重复计算。

      通过使用let 保存计算结果,您可以避免为每个数字多次计算addd

      (defn f [n k]
        (let [to-n (sum-to-n n)
              n-to-k (sum-n-to-k n k)]
          (cond
            (= to-n n-to-k) [n k]
            (> n-to-k to-n) (recur (inc n) k)
            :else (recur n (inc k)))))
      

      【讨论】:

      • 这就是我想要的,但是效率不是很低吗?如果我理解正确的话,你每次调用 f 时都会重复很多工作。有没有办法将 n 和 k 的下一个值设置为之前的值 + 1?
      • @armincerf iterate 本身并不是低效的,在示例中 anon 函数将被调用 4 次,每次使用前一个值。然而,f 函数确实会重复一些计算。
      • @armincerf 我已经对我的答案进行了编辑。你是这个意思吗?
      • 好的,我认为我有一个很好的解决方案,但无法让它运行...我可能在做一些愚蠢的事情,我进行了编辑并将我的代码添加到问题中
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-09
      • 2011-03-15
      • 2011-06-26
      相关资源
      最近更新 更多