[racket] exercise problem

From: Marijn (hkBst at gentoo.org)
Date: Wed Feb 22 11:50:39 EST 2012

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Roelof,

On 22-02-12 12:49, Roelof Wobben wrote:
> I found a working solution but I wonder if there is a better way
> to achieve the answer in the Beginning Student modus.

Allow me to propose an alternative solution and perform some
constructive criticism of your own code that may help you to improve
your style. (I don't know for certain that my solution works in BSL,
but I didn't use anything fancy, so my guess is it does.)

My proposal would be to first restate the problem as simply as
possible, such that the solution isn't solving an unnecessarily hard
problem.

So in my view the CC company pays back:
- - a quarter of a percentage of the total charge
- - a quarter of a percentage of the total charge over 500
- - a quarter of a percentage of the total charge over 1500
- - a quarter of a percentage of the total charge over 2500

with that view the solution looks like:


(define (linear-pay-back charge rate)
  (* rate (max 0 charge)))

(define (pay-back charge)
  (+ (linear-pay-back charge .0025)
     (linear-pay-back (- charge 500) .0025)
     (linear-pay-back (- charge 1500) .0025)
     (linear-pay-back (- charge 2500) .0025)))


Now if you go back to the original problem description you might say
that payback should look like this:


(define (pay-back charge)
  (+ (linear-pay-back charge .0025 0 500)
     (linear-pay-back charge .0050 500 1500)
     (linear-pay-back charge .0075 1500 2500)
     (linear-pay-back charge .0100 2500 +inf.0)))


which is equally simple as above, but linear-pay-back becomes a bit
more complicated:


(define (linear-pay-back charge rate min-charge max-charge)
  (if (< min-charge charge)
      (* rate (- (min charge max-charge) min-charge))
      0))


although it could also be written as:


(define (linear-pay-back charge rate min-charge max-charge)
  (* rate (max 0 (- (min charge max-charge) min-charge))))


and we see that our alternate-problem-description linear-pay-back is a
specialization of this one with min-charge 0 and max-charge +inf.0.
The total solution with rackunit test harness would then look like:


#lang racket

(require rackunit)

(define (linear-pay-back charge rate min-charge max-charge)
  (* rate (max 0 (- (min charge max-charge) min-charge))))

(define (pay-back charge)
  (+ (linear-pay-back charge .0025 0 500)
     (linear-pay-back charge .0050 500 1500)
     (linear-pay-back charge .0075 1500 2500)
     (linear-pay-back charge .0100 2500 +inf.0)))

(define epsilon 0 #;.00000000000000000000001)

(check-= (pay-back 500) 1.25 epsilon)
(check-= (pay-back 1500) 6.25 epsilon)
(check-= (pay-back 2000) 10 epsilon)
(check-= (pay-back 2600) 14.75 epsilon)

> The solution I found is this one:
> 
> 
> (define (tarief4 amount) (*(- amount 2500)0.01))

comparing to my solution I notice that this produces the wrong answer
for negative amounts, but maybe you consider those out of scope. (In
my solution it is necessary that I do handle that case, because I
regularly feed negative amounts to my version of this function (at
least in the first/alternate solution).)

> (define (tarief3 amount) (cond [ ( < amount 2500)(* (- amount
> 1500)0.0075)] [ else (  * .0075 1000)]))

See how in both branches you basically perform the same
multiplication? In the first branch you know that the amount is less
than 2500 and you proceed to take 3/4 percent of (- amount 1500), in
the other branch you (know that the amount is over 2500 and proceed
to) take 3/4 percent of 1000. 1000 is the maximum that (- amount 1500)
can ever be, thus you can combine both calculations to (* .0075 (min
1000 (- amount 1500)) and get rid of the whole cond (getting something
very similar to tarief4).

> (define (tarief2 amount) (cond [ ( < amount 1500)(* ( - 1000
> amount) .0050) ] [ else ( * 1000 0.0050)]))

Look how similar tarief2 is to tarief3? Wouldn't it make sense to have
a single tarief function with some extra arguments determining the
cutoffs and rates?

> (define (tarief1 amount) (cond [ (< amount 500) (* 0.0025 amount)] 
> [ else (* 0.0025 500)] ))
> 
> 
> (define (payback amount) (cond [ (<= amount 500) (tarief1 amount)] 
> [ (and ( <= amount 1500) (> amount 500)) (+ (tarief1
> amount)(tarief2 amount))] [ (and ( <= amount 2500) (> amount 1500))
> (+ (tarief3 amount) (tarief2 amount) (tarief1 amount))] [ else (+
> (tarief4 amount)(tarief3 amount)(tarief2 amount)(tarief1 amount))] 
> ) )

Your payback function is a bit of a mess, because you have
conditionals at a high level that make you duplicate a lot of code. In
a similar way as I described for tarief3 you can push the conditionals
down to expose the summation at the top level. Notice how all branches
are summing tarief1 through tarief4?! By bringing the summation to the
fore you push some logic down into your new unified tarief function
which might have to become a little smarter, but there will be less
code overall and the code that is there will be easier to understand.

Met vriendelijke groeten,

Marijn
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.18 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk9FHN8ACgkQp/VmCx0OL2ypOACfWTSGG9M3qzyKeK/+wJdhWsqi
qfEAn0Z0eHII0wuiHwy1Njs3/buvV1e6
=/HgB
-----END PGP SIGNATURE-----

Posted on the users mailing list.