Arc Forumnew | comments | leaders | submitlogin
Updated range with step
7 points by bgutierrez 6137 days ago | 11 comments
I've updated the range function to accept a step parameter:

  (def range (start end (o step 1))
     "Return a range of numbers from `start' to `end', by `step'. Default `step' is 1."
     (if (is step 0) (list start)
       (if ((if (> step 0) > <) start end)
         nil
         (cons start (range (+ start step) end step)))))
Feedback welcome.


7 points by oddbod 6136 days ago | link

    (def range (start end (o step (if (< start end) 1 -1)))
         (if (or (and (> step 0) (~> start end))
	         (and (< step 0) (~< start end)))
	     (cons start (range (+ start step) end step))))

  arc> (range 0 2)
  (0 1 2)

  arc> (range 2 0)
  (2 1 0)

  arc> (range 0 -2)
  (0 -1 -2)

  arc> (range 0 -9 -2)
  (0 -2 -4 -6 -8)

-----

4 points by rkts 6136 days ago | link

It seems wrong to allow a negative step if the direction is inferred. How about this:

  (def unfold (f init)
    (aif (f init)
      (cons (car it) (unfold f (cdr it)))
      nil))

  (def range-up (start end (o step 1))
    (unfold [if (<= _ end) (cons _ (+ _ step))] start))

  (def range-down (start end (o step 1))
    (unfold [if (>= _ end) (cons _ (- _ step))] start))

  (def range (start end (o step 1))
    ((if (< start end) range-up range-down) start end step))
Example:

  arc> (range 1 100 10)
  (1 11 21 31 41 51 61 71 81 91)

  arc> (range 1 -100 10)
  (1 -9 -19 -29 -39 -49 -59 -69 -79 -89 -99)

-----

1 point by bgutierrez 6136 days ago | link

Ah, I like that better.

-----

1 point by parenthesis 6136 days ago | link

A bit ugly:

  ;tail recursive, trickery to avoid reversing acc
  (def range (start end (o step (if (< end start) -1 1)))
    (= step (- step)
       end (+ end (mod (- start end) step)))
    ((afn (end acc)
       (if (or (and (< 0 step)
                    (<= end start))
               (and (< step 0)
                    (<= start end)))
           (self (+ end step) (cons end acc))
           acc))
     end nil))


  ;iterative
  (def range (start end (o step (if (< start end) 1 -1)))
    (let result nil
      (repeat (+ (truncate (/ (- end start)
                              step))
                 1)
        (push start result)
        (++ start step))
      (rev result)))

-----

1 point by greatness 6136 days ago | link

A little prettier perhaps, though follows the same idea:

  (def range (st en (o step 1))
    (if (is step 0) (= step 1) (zap abs step))
    (let (stp-fn test) (if (> st en) `(,- ,<) `(,+ ,>))
       ((afn (i accum)
          (if (test i en) (rev accum)
                          (self (stp-fn i step) (push i accum))))
        st nil)))
If what order you want the range doesn't matter, you can remove the (rev accum) and replace it with accum. Example output:

  arc> (range 0 -5)
  (0 -1 -2 -3 -4 -5)
  arc> (range 5 5)
  (5)
  arc> (range 0 5)
  (0 1 2 3 4 5)
  arc> (range 0 10 2)
  (0 2 4 6 8 10)
  arc> (range 10 0 2)
  (10 8 6 4 2 0)
EDIT: added some error-checking, changing a step from 0 to 1 probably isn't the right thing to do, but I didn't feel like figuring out how to throw an error.

-----

1 point by greatness 6136 days ago | link

Turns out consing up the list is faster, though without the rev function it is very close:

  (def range (st en (o step 1))
    (if (is step 0) (= step 1) (zap abs step))
      (let (stp-fn test) (if (> st en) `(,- ,<) `(,+ ,>))
        ((afn (i)
           (if (test i en) nil
                           (cons i (self (stp-fn i step)))))
         st)))
though I don't know why.

-----

1 point by joseph 6135 days ago | link

How about a default start of 0?

  (def range (default (o end) (o step 1))
    (with (st (if end default 0)
           en (if end end default))
      (if (is step 0) (= step 1) (zap abs step))
      (let (stp-fn test) (if (> st en) `(,- ,<) `(,+ ,>))
         ((afn (i accum)
            (if (test i en) (rev accum)
                            (self (stp-fn i step) (push i accum))))
          st nil))))

-----

1 point by bogomipz 6136 days ago | link

I'll just throw in that you can simplify the nested ifs slightly like this;

  (if (is step 0) (list start)
      ((if (> step 0) > <) start end) '()
      (cons start (range (+ start step) end step)))
Also, you might want to make the function tail recursive, but that's a criticism of the original rather than your addition.

-----

1 point by bgutierrez 6136 days ago | link

Isn't this exactly the same as what I did, except with different formatting and a '() in place of a nil?...

-----

1 point by ryantmulligan 6136 days ago | link

the second line has less ifs

-----

3 points by pg 6136 days ago | link

good idea

-----