0
votes

I am new to the Scheme language. I want to make a program which calculates tax with some condition. For example:

Price < 50   --------------------------------------->  Tax = %15

Price > 50 && Prize < 100  ------------------> Tax = %10

Price > 100  -------------------------------------->  Tax = %5 

Is there any way to make this calculation without using if/switch/condition statements in DrRacket & Scheme?

3
I think you mean Price >= 50 && Price < 100 and Price >= 100? Otherwise there are "holes" at 50 and 100. - Greg Hendershott

3 Answers

3
votes

Reluctant to answer since it's homework, but...

First, here's how I would write this using cond:

#lang racket
(require rackunit)

(define (tax-rate price)
  (cond [(< price  50) 0.15]
        [(< price 100) 0.10]
        [else          0.05]))

(check-equal? (tax-rate   1) 0.15)
(check-equal? (tax-rate  50) 0.10)
(check-equal? (tax-rate 100) 0.05)

Next let's think about cond. It's saying, "If the price is < 50 then the tax rate is 0.15 or if the price is < 100 then the tax rate is 0.10, or if ... and so on ...."

Notice the "or"s in the sentence. It sure sounds like a cond is a kind of (or ...) expression. The first true thing "wins".

With that hint, maybe you can figure it out? Otherwise, spoiler alert.




You could rewrite the cond using or and and:

(define (tax-rate2 price)
  (or (and (< price  50) 0.15)
      (and (< price 100) 0.10)
      0.05))

(check-equal? (tax-rate2   1) 0.15)
(check-equal? (tax-rate2  50) 0.10)
(check-equal? (tax-rate2 100) 0.05)

In general it's possible to rewrite a cond as an or, provided you don't need any of the branches except the last to return #f. Since tax-rate never needs to returns #f, it's possible here.

2
votes

Assuming you are allowed to use min:

(define (tax price)
  (/ (* price (- 15 (* 5 (min 2 (quotient price 50)))))
     100.0))
  • For values < 50, (quotient price 50) is 0, so the tax is (15-0*5)% = 15%.
  • For values >= 50 and < 100, (quotient price 50) is 1, so the tax is (15-1*5)% so 10%.
  • For values >= 100, (quotient price 50) is 2 or higher. Since we don't go below 5%, I use min to reduce the result to 2 if necessary, and the tax is (15-2*5)% = 5%.

Testing:

> (tax 30)
4.5
> (tax 70)
7.0
> (tax 120)
6.0

and comparing with an algorithm using conditionals:

(for ((n 2000))
  (let ((tax1 (tax n))
        (tax2 (/ (* n (cond
                        ((< n 50) 15)                         
                        ((< n 100) 10)
                        (else 5)))
                 100.0)))
    (unless (= tax1 tax2)
      (printf "~a: fun= ~a   man= ~a \n" n tax1 tax2 ))))

shows no difference with the formula using cond (at least for values of price from 0 to 1999).

1
votes

There is indeed a way to not use any condition at all, even predefined functions that use conditions like min, but if you're new to Scheme, you're not going to like it, and it may not be what you are after. The trick is to go back to bare bones lambda calculus, from which Scheme is derived.

First, redefine the primitives for true and false, as functions that return either the first or the second argument:

(define TRUE  (lambda(t f) t))
(define FALSE (lambda(t f) f))

Second, define the number zero as the list containing only FALSE (we could even avoid using list primitives, but for a simple example this will do), and define two functions to increment and decrement a number:

(define zero (list FALSE))
(define (inc n) (cons TRUE n))
(define (dec n) (rest n))

Define some numbers:

(define one   (inc zero))
(define two   (inc one))
(define three (inc two))

So the first element of any (positive) number that is not zero is TRUE, and it is FALSE for zero. You can use this information to define a function that takes a number and two functions as argument, and applies the first function if the number is positive, and the second one otherwise:

(define (if-positive n proc-true proc-false)
  (((first n)
    proc-true
    proc-false)))

;; TESTS
(if-positive three (lambda() 'positive) (lambda() 'zero)) ; -> 'positive
(if-positive zero  (lambda() 'positive) (lambda() 'zero)) ; -> 'zero

Now, we can make a function that takes two numbers and two procedures, recursively subtract 1 from each number and if the first number reaches 0 first it applies the first procedure, but if the second number reaches 0 first it applies the second procedure:

(define (if-greater n1 n2 proc-true proc-false)
  (if-positive 
   n1 
   (lambda()(if-positive 
        n2
        (lambda()(if-greater (dec n1) (dec n2) proc-true proc-false))
        proc-true))
   proc-false))

;; TESTS
(if-greater three two (lambda() 0.15) (lambda() 0.1)) ; -> 0.15
(if-greater one   two (lambda() 0.1)  (lambda() 0.05)) ; -> 0.05

Now all you need to do is define the numbers 100 and 50 and you're good to go. Of course this is very impractical, but that's what you get by not using if. (Note that it could be made slightly more practical by using base 2 numbers instead of base 1, but for this example this is a detail.)

More information here for example, but it may be a bit harsh on a newcomer.