Search code examples
clojurefunctional-programming

How to return a lazy sequence from a loop recur with a conditional in Clojure?


Still very new to Clojure and programming in general so forgive the stupid question.

The problem is:

Find n and k such that the sum of numbers up to n (exclusive) is equal to the sum of numbers from n+1 to k (inclusive).

My solution (which works fine) is to define the following functions:

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

And then run the following loop:

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

This only returns one answer but if I manually set n and k I can get different values. However, I would like to define a function which returns a lazy sequence of all values so that:

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

How do I do this as efficiently as possible? I have tried various things but haven't had much luck.

Thanks

:Edit:

I think I came up with a better way of doing it, but its returning 'let should be a vector'. Clojure docs aren't much help...

Heres the new code:

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

Solution

  • If you don't feel like "rolling your own", here is an alternate solution. I also cleaned up the algorithm a bit through renaming/reformating.

    The main difference is that you treat your loop-recur as an infinite loop inside of the t/lazy-gen form. When you find a value you want to keep, you use the t/yield expression to create a lazy-sequence of outputs. This structure is the Clojure version of a generator function, just like in 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))))))
    

    with results:

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

    You can find all of the details in the Tupelo Library.