1

I have written a function (listed below) which returns a function that returns n items from an infiniate set as defined in the comments.

; Returns a function which in turn returns a vector of 'n' items, with init-val being the 'middle' value,
; Values to the 'right' of init-val being 'step' added to the (previously) last value, and items 'left' of
; 'init-val' being 'step' subtracted from the (previously) first value in the list. 
(defn range-right-left [init-val step] 
(fn [n] ; produce a vector containing 'n' items
    (loop [Vector (cond (zero? n) [] 
                (pos? n)  [init-val] 
                :else  nil)]
        (if (or (= (count Vector) n) (nil? Vector)) Vector ; base case(s)
            (recur
                (if (odd? (count Vector))
                    (conj Vector (+ (last Vector) step)) ; add 'step' to the last element of the vector and place it on the back of the vector 
                    (vec (cons (- (first Vector) step) Vector)))))))) ;else if Vector contains an even number of items, subtract step from the first item in the vector and place it on front of the resulting collection (converting to vector)

To clarify the behavior of the function, I am including the code for the tests (which all passed).

(deftest range-right-left-test
    (is (= nil ((range-right-left 7 3) -1)))
    (is (= [] ((range-right-left 7 3)  0)))
    (is (= [7] ((range-right-left 7 3)  1)))
    (is (= [7 10] ((range-right-left 7 3)  2)))
    (is (= [4 7 10] ((range-right-left 7 3)  3)))
    (is (= [4 7 10 13] ((range-right-left 7 3)  4)))
    (is (= [1 4 7 10 13] ((range-right-left 7 3)  5))))

What I would really like, though, is for 'range-right-left' to return a lazy sequence instead of a function. In other words, instead of doing this:

((range-right-left 7 3) 3)

I would like to be able to do:

(take 3 (range-right-left 7 3))

It seems to be the default behavior for lazy sequences to strictly grow from left to right. I have tried to develop a lazy seq that can grow in both directions, to no avail. I would very much appreciate suggestions to do such.

satch5150
  • 51
  • 1
  • 2
    Lazy sequences can only grow in one direction because they have a lazy tail. Once you have the head of a lazy sequence, you can't go back and "lazily" change it because it's already been evaluated. What are you trying to do here? It seems like you should be able to sove your `range-right-left` problem with 2 ranges instead of 1. If you provide more details about the use case we might be able to suggest an alternate solution. – DaoWen Jul 29 '13 at 21:46
  • One use case is to be able to get n items from the equivalence class defined by 'r (mod m)'. 'r' would be the starting middle value. The next item to the left would be obtained by subtracting m from the first of the list. Likewise, the next item to the right would be obtained by adding m to the last item in the list. I would like to have this behavior defined in a lazy-sequence so that I can be more idiomatic about using such a sequence (i.e. for n items being able to say (take 3 seq) and give me n items in the way I have specified. – satch5150 Jul 29 '13 at 22:51
  • To clarify my above remarks, what I'm looking for is to be able to obtain n items from the equivalence class defined by 'r (mod m)' without having to specify a 'least' element, so that I am able to obtain items from both ends of the 'spectrum', as it were. – satch5150 Jul 29 '13 at 23:02

3 Answers3

3

You could try using the approach of zippers.

(defn left [[left v right]]
  [(next left) (first left) (cons v right)])
(defn right [[left v right]]
  [(cons v left) (first right) (next right)])
(defn curr [[left v right]] v)

So now we can define your function as

(defn range-right-left [init-val step]
  [(next (iterate #(- % step) init-val))
   init-val
   (next (iterate #(+ % step) init-val))])

Then we can take any given element by using left and right to move our view of our collection and curr to extract the current element:

(def zipper (range-right-left 4 10))
(-> zipper left left left curr) ; => -26

We can also create two utility functions:

(defn left-seq [[left v right]]
  (cons v left))
(defn right-seq [[left v right]]
  (cons v right))

which gives us the ability to do something close to what you wanted

(take 3 (left-seq (range-right-left 7 3))) ; => (7 4 1)
(take 3 (right-seq (range-right-left 7 3))) ; => (7 10 13)

Note: zippers are far more general than what you're doing here, so this approach may be overkill for your particular use. If your ordering is unimportant to you then I would recommend just using DaoWen's approach of interleaving the two sides of the sequence.

mange
  • 3,172
  • 18
  • 27
  • So this essentially lets you build a tree structure instead of a linear sequence, right? – DaoWen Jul 30 '13 at 00:29
  • More generally it's a tree structure, but in this restricted case it's a linear sequence extending (lazily) in both directions. – mange Jul 30 '13 at 00:29
  • It might conceptually work like a linear sequence, but the underlying structure is definitely a tree. :-p – DaoWen Jul 30 '13 at 00:32
  • 1
    @DaoWen It's sorta an undefinable philosophical point, but I wouldn't say this is a tree structure. The zipper he builds is a 3-tuple, consisting of a seq, a value, and a seq. To move left, he constructs a new tuple by shuffling around the nodes in those lists. At no point does one of these zipper-tuples contain a reference to any other, so there's really no tree structure at all as far as I can see, just a series of references to linear lists. – amalloy Jul 31 '13 at 02:14
  • @amalloy - Thanks for the clarification. I didn't read the code closely enough to realize that the tuples never contained any mutual references. – DaoWen Jul 31 '13 at 03:02
1

If all you want is to get n members of an equivalence class, then you could define your function like this instead:

(defn range-right-left [x step]
  (interleave (iterate #(- % step) x)
              (iterate #(+ % step) (+ x step))))

(take 5 (range-right-left 7 3))
; => (7 10 4 13 1)

(sort (take 5 (range-right-left 7 3)))
; => (1 4 7 10 13) 

The results aren't in the same order that you had before, but as the example shows you can just use sort once you have your slice of the infinite stream if you really need it in sorted order.

DaoWen
  • 32,589
  • 6
  • 74
  • 101
0

I'd recommend to do this:

(defn from-right-left-range
  "Take n items from a range expanding from init by step in both
  directions."
  [init step n]
  (cond
    (= 0 n) '()
    (= 1 n) (list init)
    :else (let [d (* step (dec n))]
            (concat [(- init d)]
                    (from-right-left-range init step (dec n))
                    [(+ init d)]))))

This recursive function can be memoized and will run faster.

(dotimes [_ 5]
  (time (from-right-left-range 7 3 100)))
=> "Elapsed time: 0.129167 msecs"
   "Elapsed time: 0.065219 msecs"
   "Elapsed time: 0.061449 msecs"
   "Elapsed time: 0.069579 msecs"
   "Elapsed time: 0.060461 msecs"

Now memoized:

(def from-right-left-range (memoize from-right-left-range))
(dotimes [_ 5]
  (time (from-right-left-range 7 3 100)))
=> "Elapsed time: 0.297716 msecs"
   "Elapsed time: 0.038473 msecs"
   "Elapsed time: 0.013715 msecs"
   "Elapsed time: 0.010902 msecs"
   "Elapsed time: 0.010372 msecs"

Depending on what you originally wanted to achieve with the use of a lazy-seqs, if it was performance, this is your solution.

Now what you can do is create a lazy-seq of like

(defn right-left-ranges
  [init step]
  (map (partial from-right-left-range init step) (range)))

You can use (nth ls n) on that lazy-seq like you'd use (take n ls) on lazy-seqs to get your double-grown seq. Thanks to memoize, only the not yet calculated missing elements at the beginning and end will be added.

Leon Grapenthin
  • 9,246
  • 24
  • 37