如何使用Clojure中的条件从循环重复返回一个惰性序列?

时间:2017-09-26 20:09:13

标签: clojure functional-programming

对于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 docs没什么帮助......

继承新代码:

(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))

4 个答案:

答案 0 :(得分:2)

是的,您可以创建一个lazy-seq,以便下一次迭代将获取上一次迭代的结果。这是我的建议:

(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])

答案 1 :(得分:1)

只使用iterate生成候选对象的懒惰seq然后过滤它们应该是你需要的:

(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])

在语义上它就像手动生成一个lazy-seq一样,应该同样有效,但这个可能更惯用

答案 2 :(得分:0)

这个第一个函数可能有更好的数学名称,但我不太了解数学。我使用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)))))

答案 3 :(得分:-1)

如果您不想“自己动手”,这是另一种解决方案。我还通过重命名/重新格式化来清理算法。

主要区别在于您将loop-recur视为t/lazy-gen形式内的无限循环。当您找到要保留的值时,可以使用t/yield表达式来创建延迟的输出序列。此结构是 生成器函数的Clojure版本

(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))))))

(t/spyx (take 5 (recur-gen)))

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