5
\$\begingroup\$

I am using the book "Realm of Racket" which was written by the people behind the Racket and Dr. Racket projects. It is a great book based on games, similar to the famous "Land of Lisp".

The first game we are supposed to code is the classic "Guess My Number". Following the instructions on the book you end up with this code:

(define lower 1)

(define upper 100)

(define (computer-guess)
  (round (/ (+ lower upper) 2)))

(define (smaller)
  (set! upper (max lower (sub1 (computer-guess))))
  (computer-guess))

(define (bigger)
  (set! lower (min upper (add1 (computer-guess))))
  (computer-guess))

(define (start n m)
  (set! lower (min n m))
  (set! upper (max n m)))

As a matter of practice I decided to try something different. On the previous code, you need to keep using the interactive panel to make new guesses until you reach the number.

As you can see, the algorithm uses binary search.

I am trying the best I can to practice the concept of a recursive definition which generates an iterative procedure.

Hence, I tried to create a code which would keep making new guesses until reaching the determined number. I also tried to create a code that would count the number of tries.

I ended up with the following code which works!

Despite providing the expected output and working fine, I think I could do some refactoring.

I am repeating my self but I do not see how to avoid this:

#lang racket

(define (guess lower-boundary upper-boundary)
  (round (/ (+ lower-boundary upper-boundary) 2)))

(require racket/trace)

(define (game-iter number)
  (define (iter number count lower-boundary  upper-boundary accu)
    (cond ((= (guess lower-boundary upper-boundary) number) (values count (guess lower-boundary upper-boundary)))
          ((> (guess lower-boundary upper-boundary) number)
           (iter  number
                  (add1 count)
                  lower-boundary
                  (sub1 (guess lower-boundary upper-boundary))
                  (guess lower-boundary upper-boundary)))
          (else (iter number (add1 count) (add1 (guess lower-boundary upper-boundary)) upper-boundary (guess lower-boundary upper-boundary)))))
  (trace iter)
  (iter number 0 1 100 (guess 1 100)))

(game-iter 10)
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Racket isn't my Lisp of choice, but here's some general advice.

You're calling the function guess multiple times with the same arguments. The code would be much more readable (and slightly more efficient) if you bind the result to a local variable:

(define (game-iter number)
  (define (iter number count lower-boundary upper-boundary accu)
    (let ((guess (guess lower-boundary upper-boundary)))
      (cond ((= guess number) (values count guess))
            ((> guess number)
             (iter  number
                    (add1 count)
                    lower-boundary
                    (sub1 guess)
                    guess))
            (else (iter number 
                        (add1 count) 
                        (add1 guess) 
                        upper-boundary 
                        guess)))))
  (trace iter)
  (iter number 0 1 100 (guess 1 100)))

I'm not sure if the racket custom is to abbreviate variable names to avoid shadowing the function. Using the same name for the variable and the function shouldn't cause any problems in this case, so I'll just use guess here. Feel free to change it.

Your iter function has number as a parameter, but it never changes. It's unnecessary to pass it down, since it's already available from the outer scope. You also have a parameter called accu, which is never used. Unless you intend to use it later, it should be removed.

(define (game-iter number)
  (define (iter count lower-boundary upper-boundary)
    (let ((guess (guess lower-boundary upper-boundary)))
      (cond ((= guess number) (values count guess))
            ((> guess number)
             (iter (add1 count)
                   lower-boundary
                   (sub1 guess)))
            (else (iter (add1 count) 
                        (add1 guess) 
                        upper-boundary)))))
  (trace iter)
  (iter 0 1 100))

You coud collapse the two calls to iter into one by using if inside the call.

(define (game-iter number)
  (define (iter count lower-boundary upper-boundary)
    (let ((guess (guess lower-boundary upper-boundary)))
      (if (= guess number)
          (values count guess)
          (iter (add1 count)
                (if (< guess number)
                    (add1 guess)
                    lower-boundary)
                (if (> guess number)
                    (sub1 guess)
                    upper-boundary)))))
  (trace iter)
  (iter 0 1 100))

Notice also that you start counting guesses from zero. So if the number is 50, which is the first guess, the program will consider that to be zero guesses. It would make more sense to start from 1.

As pointed out by Renzo, you should also make sure that the number is in the range from 1 to 100:

(define (game-iter number)
  (define (iter count lower-boundary upper-boundary)
    (let ((guess (guess lower-boundary upper-boundary)))
      (if (= guess number)
          (values count guess)
          (iter (add1 count)
                (if (< guess number)
                    (add1 guess)
                    lower-boundary)
                (if (> guess number)
                    (sub1 guess)
                    upper-boundary)))))
  (trace iter)
  (if (< 0 number 101)
      (iter 1 1 100)
      ;; Raise an error or return something suitable
      #f))
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Maybe you could add an initial test to see if number is inside the range 1 100, otherwise the program will loop. \$\endgroup\$
    – Renzo
    Oct 12, 2016 at 5:13
  • \$\begingroup\$ @jkiiski, great answer. I learned a lot. Thanks! \$\endgroup\$
    – user119912
    Oct 12, 2016 at 15:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.