recursive function with multiple arguments

225 views
Skip to first unread message

travis.h...@gmail.com

unread,
Apr 11, 2019, 1:50:23 AM4/11/19
to Racket Users
Hi All,

I'm trying to better understand recursion. I decided to rewrite the logmodr function from this blog post as a recursive function. I was easily able to write the recursive functions if all but one of the arguments were set to default values. But I was completely stumped by how to handle the situation without using default argument values. After a considerable amount of flailing about, I finally realized that I could accomplish this task by passing the arguments as a struct (see code below). I find the struct approach relatively intuitive but it strikes me as quite verbose. Are there other (better?) ways to handle this task?

Thanks,

Travis


#lang racket

(require math)

(struct args (y r k thetasd) #:transparent) ; only 'y' changes in project-pop function below

(define (logmod args-struct)
  (define theta (flvector-ref (flnormal-sample 0.0 (args-thetasd args-struct) 1) 0))
  (args (* (args-y args-struct) (* (- (args-r args-struct) (* (args-r args-struct) (/ (args-y args-struct) (args-k args-struct)))) (exp theta)))
        (args-r args-struct)
        (args-k args-struct)
        (args-thetasd args-struct)))

(define (project-pop args-struct t [i 1])
  (define y (args-y args-struct))
  (if (= i t)
      (list y)
      (cons y (project-pop (logmod args-struct) t (+ i 1)))))
      
(project-pop (args 1.0 1.8 20.0 0.1) 25)


Matt Jadud

unread,
Apr 11, 2019, 7:02:05 AM4/11/19
to travis.h...@gmail.com, Racket Users
Hi Travis,

Others will probably have better insights.

The only part that would be able to be replaced with a recursive call in logmodr seems to be the loop itself. It is walking the list/vector y from the second element to t (which... does numeric on a vector coerce it to a length? I have no idea what numeric(t) does in this context. I sometimes lose my mind trying to think about everything in R as a vector, and where/when different functions do different things depending on what you pass as a value...). 

My termination condition is when i is equal to t, and otherwise, I do the calculation, attaching the current calculation to the result of calculating the rest of the values in the range. (In the for loop in the original logmodr, this comes out looking like assignment to a vector, but we'll cons up a list instead.) I have no idea if my pseudocode below puts the list in the correct order... that could be fixed in a number of ways.

If I were translating this code, I would personally consider allocating the vector and just doing the direct transliteration with a for loop. I don't know that I would try and convert the code to a recursive call... but, as a learning exercise, it makes sense why you're thinking about it.

The HtDP design recipe helps in thinking about how to break these kinds of questions down. In this case, I had to think about whether there was a recursion over a list/vector (there doesn't seem to be), but instead it seemed like I'm building up my results in a list/vector. Therefore, I needed to think about the recursion on the range of numbers and choose my termination condition accordingly. From there, I also needed to figure out what information was initialization, and what information needed to be passed through the loop. Ultimately, I might pass a fair number of parameters, but that's because I have to maintain the correct local scope for each recursive call. I could also use an internal function definition for some of these things, so that values that don't change are defined once, and the function that is implementing the loop closes over those values just once. This would cut... theta from the list of parameters, and possibly also k and r, because they don't change. (Ha! K&R don't change. That's a C pun!) 

Bad humor aside, my point was that I could probably define a calc function that had an internal calc* function (using either an internal define or a letrec), and it would only take i and y as parameters. calc* would then implement the "loop".

Someone will, no doubt, post a cleaner example that is three lines long and only uses letrec and lambda, but here was my take on your question. But, this was more fun then getting started on grading.

Cheers,
Matt

(define (rnorm t thetasd)
  (list 1 2 1))

(define (calc i y t r k theta)
  (cond
    [(= i t) '()]
    [else
     (cons (* (list-ref y (sub1 i))
              (- r (* (/ (* r (list-ref y (sub1 i))) k)
                      (exp (list-ref theta i)))))
           (calc (add1 i) y t r k theta))]))
                 
(define (logmodr yinit t r k thetasd)
  (let ([y (list yinit)]
        [theta (rnorm t 0 thetasd)])
    (calc 0 y t r k theta)))

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alex Harsanyi

unread,
Apr 11, 2019, 8:10:31 AM4/11/19
to Racket Users


On Thursday, April 11, 2019 at 1:50:23 PM UTC+8, travis....@gmail.com wrote:
Hi All,

I'm trying to better understand recursion. I decided to rewrite the logmodr function from this blog post as a recursive function. I was easily able to write the recursive functions if all but one of the arguments were set to default values. But I was completely stumped by how to handle the situation without using default argument values. After a considerable amount of flailing about, I finally realized that I could accomplish this task by passing the arguments as a struct (see code below). I find the struct approach relatively intuitive but it strikes me as quite verbose. Are there other (better?) ways to handle this task?

The trick is to use nested functions, like so:

(define (project-pop-1 y r k thetasd t)

 
(define (logmod y)
   
(define theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0))
   
(* y (- r (* r (/ y k))) (exp theta)))
   
 
(define (loop y i)

   
(if (= i t)
       
(list y)

       
(cons y (loop (logmod y) (add1 i)))))

 
(loop y 1))

(project-pop-1 1.0 1.8 20.0 0.1 25)

Also, multiplication accepts any number of arguments, instead of (* a (* b c)) when you can write (* a b c)

Also, if you want to use structures, you can use `struct-copy` and `match-define` and the code looks much nicer.  Your implementation of logmod can be written as:

(define (logmod-1 args-struct)
 
(match-define (args y r k thetasd) args-struct)
 
(define theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0))
 
(define new-y (* y (- r (* r (/ y k))) (exp theta)))
 
(struct-copy args args-struct [y new-y]))
Alex.

David Storrs

unread,
Apr 11, 2019, 12:11:25 PM4/11/19
to travis.h...@gmail.com, Racket Users
Others have already given elegant solutions, but if you'd like a simple transliteration that does not require a struct, here you go:

#lang racket
(require math)

(define (logmod y r k thetasd)
  (define theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0))
  (list (* y

           (- r (* r (/ y k)))
           (exp theta))
        r k thetasd))

(define (project-pop y r k thetasd t [i 1])

  (if (= i t)
      (list y)
      (cons y (apply project-pop (append (logmod y r k thetasd) (list t (+ i 1)))))))

(project-pop  1.0 1.8 20.0 0.1 25)

====================
If you'd like to reduce it to one function, you could do this:

(define (project-pop y r k thetasd t [i 1])

  (if (= i t)
      (list y)
      (cons y (project-pop (let ([theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0)])

                                            (* y (- r (* r (/ y k))) (exp theta)))
                                       r k thetasd t
                                      (+ i 1)))))

(project-pop  1.0 1.8 20.0 0.1 25)



Matthias Felleisen

unread,
Apr 12, 2019, 11:55:11 AM4/12/19
to travis.h...@gmail.com, Racket Users

A bit of modernity yields this: 

(define (project-pop.v1 y r k thetasd t [i 1])
  (cond
    [(= i t) (list y)]
    [else (define theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0))
          (define y1 (* y (- r (* r (/ y k))) (exp theta)))
          (cons y (project-pop y1 r k thetasd t (+ i 1)))]))

Since this is really a fold, you could also use one of the Racket comprehensions to bring across the intent: 

(define (project-pop.v2 y r k thetasd t)
  (define-values (_ l)
    (for/fold ((y y) (l '())) ((i (in-range 0 t 1)))

      (define theta (flvector-ref (flnormal-sample 0.0 thetasd 1) 0))
      (values (* y (- r (* r (/ y k))) (exp theta)) (cons y l))))
  (reverse l))

Given how short these functions are, v1 is just fine. 

Or you could stick to the original R design and write an imperative vector-fill function: 

(define (project-pop.v3 y0 r k thetasd t)
  (define y (make-vector t y0))
  (for ((i (in-range 1 t 1)))
    (define theta (flnormal-sample 0.0 thetasd 1))
    (define y@i-1 (vector-ref y (- i 1)))
    (vector-set! y i (* y@i-1 (- r (* r (/ y@i-1 k))) (exp (flvector-ref theta 0)))))
  y)





Reply all
Reply to author
Forward
0 new messages