Question

I wrote this code to nest a function n times and am trying to extend the code to handle a test. Once the test returns nil the loop is stopped. The output be a vector containing elements that tested true. Is it simplest to add a while loop in this case? Here is a sample of what I've written:

(defn nester [a inter f]
(loop [level inter expr a]
    (if (= level 0) expr
    (if (> level 0) (recur (dec level) (f expr))))))

An example input would be an integer 2, and I want to nest the inc function until the output is great than 6. The output should be [2 3 4 5 6 7].

Was it helpful?

Solution

(defn nester [a inter f test-fn]
  (loop [level inter
         expr a]
    (if (or (zero? level)
            (nil? (test-fn expr)))
      expr
      (recur (dec level)
             (f expr)))))

If you also accept false (additionally to nil) from your test-fn, you could compose this more lazily:

(defn nester [a inter f test-fn]
  (->> (iterate f a)
       (take (inc inter))
       (drop-while test-fn)
       first))

EDIT: The above was answered to your initial question. Now that you have specified completely changed the meaning of your question:

If you want to generate a vector of all iterations of a function f over a value n with a predicate p:

(defn nester [f n p]
  (->> (iterate f n)
       (take-while p)
       vec))

(nester inc 2 (partial > 8)) ;; predicate "until the output is greater than six"
                             ;; translated to "as long as 8 is greater than
                             ;; the output"

=> [2 3 4 5 6 7]

OTHER TIPS

To "nest" or iterate a function over a value, Clojure has the iterate function. For example, (iterate inc 2) can be thought of as an infinite lazy list [2, (inc 2), (inc (inc 2)), (inc (inc (inc 2))) ...] (I use the [] brackets not to denote a "list"--in fact, they represent a "vector" in Clojure terms--but to avoid confusion with () which can denote a data list or an s-expression that is supposed to be a function call--iterate does not return a vector). Of course, you probably don't want an infinite list, which is where the lazy part comes in. A lazy list will only give you what you ask it for. So if you ask for the first ten elements, that's what you get:

user> (take 10 (iterate inc 2))
> (2 3 4 5 6 7 8 9 10 11)

Of course, you could try to ask for the whole list, but be prepared to either restart your REPL, or dispatch in a separate thread, because this call will never end:

user> (iterate inc 2)
> (2
   3
   4
   5
   6
   7
   8
   9
   10
   11
   12
   13
   14
   15
   16
   17
   18
=== Shutting down REPL ===
=== Starting new REPL at C:\Users\Omnomnomri\Clojure\user ===
Clojure 1.5.0
user>

Here, I'm using clooj, and this is what it looks like when I restart my REPL. Anyways, that's all just a tangent. The point is that iterate answers the core of your question. The other part, stopping upon some test condition, involves take-while. As you might imagine, take-while is a lot like take, only instead of stopping after some number of elements, it stops upon some test condition (or in Clojure parlance, a predicate):

user> (take-while #(< % 10) (iterate inc 2))
> (2 3 4 5 6 7 8 9)

Note that take-while is exclusive with its predicate test, so that here once the value fails the test (of being less than 10), it excludes that value, and only includes the previous values in the return result. At this point, solving your example is pretty straightfoward:

user> (take-while #(< % 7) (iterate inc 2))
> (2 3 4 5 6)

And if you need it to be a vector, wrap the whole thing in a call to vec:

user> (vec (take-while #(< % 7) (iterate inc 2)))
> [2 3 4 5 6]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top