Monday, 23 April 2012

Exercise 164: Argue why it is acceptable to use last on Polygons.

Exercise 164: Argue why it is acceptable to use last on Polygons. Also argue why you may reuse the template for  connect-dots for last:



; NELoP -> Posn
; to extract the last Posn on p
(define (last p)
  (first p))

If we go back to the definition of a Polygon we can see that it consists of either a list of three Posns, or
a Posn cons'd with another (valid) Polygon.  It is acceptable to use last on a polygon as it will always consist of  at least three valid Posn's and so a Posn will always be able to be safely returned. 

This question confused me at first, at in the stub function, the first p in the Polygon is returned. This is to make the stub valid as it can't just return P as then it would be returning a Polygon, rather than a Posn. However, reading a bit further it is clear that this is just a stub and not the complete function!

We are told we can re-use the template we used for the connect-dots:


(define (last p)
  (cond
    [(empty? (rest p)) (... (first p) ...)]
    [else (... (first p) ... (last (rest p)) ...)]))


The reason we can use this template is that the definition of Polygon guarantees that this will be a Non empty list of Posns.

; Polygon is one of:; 
; – (list Posn Posn Posn); 
; – (cons Posn Polygon)




Even though we're not asked to implement the function last I thought I would have a go before reading on further.


First create some simple tests:


; Check a list of 3 posns (a triangle) correctly returns the 
; last point
(check-expect (last (list (make-posn 10 10)
                          (make-posn 60 60)
                          (make-posn 10 60)))
              (make-posn 10 60))


; Check a list of 4 posns (a square) correctly returns the 
; last point
(check-expect (last (list (make-posn 10 10)
                          (make-posn 60 10)
                          (make-posn 60 60)
                          (make-posn 10 60)))
              (make-posn 10 60))
              


I chose just to use the length of the list rather than getting the rest of the list three times.  Both of these
definitions pass the tests, but looking back on the text we find this quote:

"Since all Polygons consist of at least three Posns, using rest three times is legal. Unlike length, rest is also a primitive, easy-to-understand operation with a clear operational meaning. It selects the second field in a cons structure and that is all it does."

I'm not going to argue with the author, but from my perspective, using a primitive operator is not neccessarily better than a higher level operator - especially when the latter is shorter and easier to read.

; NELoP -> Posn
; to extract the last Posn on p
(define (my_last p)
  (cond
    [(= (length p) 3) (third p)]
    [else (last (rest p))]))


; NELoP -> Posn
; extract the last Posn from p
(define (last p)
  (cond
    [(empty? (rest (rest (rest p)))) (third p)]
    [else (last (rest p))]))


Monday, 16 April 2012

Exercise 163: Adapt the second example for render-poly to connect-dots.


Exercise 163: Adapt the second example for render-poly to connect-dots.


I was unable to answer this question.  We needed to take the supplied render-poly function and convert it to use connect-dots.

This was the best I was able to come up with

; Polygon -> Image
; to render the given polygon p into MT
(define (render-poly p)
  (cond ((empty? p) p)
        (else (
               (connect-dots (first p))
               (render-poly (rest p))  
               ))))

The issue with this is that you can't call two functions in a row like that. (At least not with the knowledge we have right now)

If you try and run this, you will get an error:

function call: expected a function after the open parenthesis, but received #<image>

I don't think there is a way to do this without changing render-poly or connect-dots signatures.

Finally I had to sneak a peak at the answer... (which is luckily given shortly further down in the text)


; Polygon -> Image
; add the Polygon p into an image in MT
(define (render-polygon p)
  (render-line (connect-dots p) (first p) (last p)))

In hind-sight this is fairly obvious - or at least the part about using render-line. connect-dots is going to draw the actual polygon for us and return our image. What I don't understand is why we need to draw a line between (first p) and (last p).

Presumably the function connect-dots is going to draw all but this last line (due to the bug in the first implementation given) and we are filling in this last line manually at the end.

This is not very well explained - but hopefully it will become clear as we progress on to question 164. Either I missed something obvious in the text, or I'm not sure how we could have answered this given the information we had.

Monday, 9 April 2012

Exercise 162: Here is the function 'search'...



Exercise 162: Here is the function search:
; Number List-of-numbers -> Boolean
(define (search n alon)
  (cond
    [(empty? alon) false]
    [else (or (= (first alon) n) (search n (rest alon)))]))
It determines whether some number occurs in a list of numbers. The function may have to traverse the entire list to find out that the number of interest isn’t contained in the list.
Develop the function search-sorted, which determines whether a number occurs in a sorted list of numbers. The function must take advantage of the fact that the list is sorted.

The function we are developing here is quite similar to the last two questions. The only real difference is that we are starting to look at trying to optimise list traversal. This is simple enough to do as the list is already sorted and we can easily check where we are in the list and return as soon as we know the number can't be in the list (as we have gone past the place where the number would have to be).

Firstly we need (or desire) to include racket/base. This will give us access to fprintf - a function that can print out some diagnostic information so that we prove that we are taking advantage of the fact that the list is sorted. This is absolutely not required for the question.


(require racket/base)


; this testlist is just for ease of testing.
(define TESTLIST (list 100 80 45 23 22 20 3 1))


; tests are pretty straight forward - either the number in the list is there or it is not.
(check-expect (search-sorted 100 TESTLIST) true)
(check-expect (search-sorted 1 TESTLIST) true)
(check-expect (search-sorted 50 TESTLIST) false)




This is the search-sorted function. This is fairly straight forward. First I display some information as to what the current state of the list is and where we are at, then I recurse further down the list. If the first item on the list is larger than where we are at, then the list can't contain the n we are searching for and we immediately return false.


; Number List-of-numbers -> Boolean
(define (search-sorted n alon)
  (fprintf (current-output-port) 
           "searching for ~a First of alon: ~a. Alon is ~a \n" n (first alon) alon)
  (cond
    [(or (empty? alon)
         (> n (first alon))) false]
    [else (or (= (first alon) n) (search-sorted n (rest alon)))]))


This is the output from running out test cases on the above function. As you can see the first test case (where the n is not present in the list) iterates thru the entire list. For the second test case, as soon as we find the head of the list is less than 50, we stop recursing and return false.

Test Case 1

searching for 100 First of alon is 100.     Alon is (100 80 45 23 22 20 3 1) 
searching for 1 First of alon is 100.     Alon is (100 80 45 23 22 20 3 1) 
searching for 1 First of alon is 80.     Alon is (80 45 23 22 20 3 1) 
searching for 1 First of alon is 45.     Alon is (45 23 22 20 3 1) 
searching for 1 First of alon is 23.     Alon is (23 22 20 3 1) 
searching for 1 First of alon is 22.     Alon is (22 20 3 1) 
searching for 1 First of alon is 20.     Alon is (20 3 1) 
searching for 1 First of alon is 3.     Alon is (3 1) 
searching for 1 First of alon is 1.     Alon is (1) 


Test Case 2

searching for 50 First of alon is 100.     Alon is (100 80 45 23 22 20 3 1) 
searching for 50 First of alon is 80.     Alon is (80 45 23 22 20 3 1) 
searching for 50 First of alon is 45.     Alon is (45 23 22 20 3 1) 

Saturday, 31 March 2012

Exercise 161: Design a programe that sorts lists of game players by score.



Exercise 161: Design a program that sorts lists of game players by score:
(define-struct gp (name score))
; GamePlayer is a structure:
;  (make-gp String Number)
; interp. (make-gp p s) represents player p who scored
; a maximum of s points



This question (and solution) is again fairly similar to exercises 160 and 159. It's almost as though they wanted to drive home how similar these sorts of problems are.


Accordingly my answer is similar to the last, but we will still play along and create the solution from scratch...


; this struct is supplied by the question.
(define-struct gp (name score))


; start by creating a number of structs for easy testing etc
(define FIRST (make-gp "dave"  0 ))
(define SECOND (make-gp "bob" 10))
(define THIRD (make-gp "frank" 20))
(define FOURTH (make-gp "tom" 30))






; some tests to make sure we are doing things right.
(check-expect (sort-> empty) empty)
(check-expect (sort-> (list THIRD FOURTH FIRST)) 
              (list FOURTH THIRD FIRST))
(check-expect (sort-> (list THIRD SECOND FIRST)) 
              (list THIRD SECOND FIRST))
(check-expect (sort-> (list FIRST SECOND THIRD)) 
              (list THIRD SECOND FIRST))






This is the basic sort function - as you can see it is more or less identical to the last solution.



; List-of-gps -> List-of-gps
; produces a version of a gps (game player score), sorted by score in 
; descending order

(define (sort-> gps)
  (cond
    [(empty? gps) empty]
    [else (insert (first gps) (sort-> (rest gps)))]))


; Gps, List-of-Gps -> List-of-Gps 
; a helper function to do the actual insert
; insert n into the sorted list of gps's
(define (insert n alogps)
  (cond [(empty? alogps) (cons n empty)]
        [else (cond [(higher-score? n (first alogps)) (cons n alogps)]
                    [else (cons (first alogps) 
                                (insert n (rest alogps)))])]))


; utility function to determine which of two games had the higher score
; we could do this inline, but this lends itself to more clarity i think
(define (higher-score? game-1 game-2)
  (>= (gp-score game-1) (gp-score game-2)))

Tuesday, 27 March 2012



Exercise 160: Design a program that sorts lists of mail messages by date:
(define-struct mail (from date message))
; Mail Message is a structure:
;  (make-mail String Number String)
; interp. (make-mail f d m) represents text m sent by
; fd seconds after the beginning of time
Also develop a program that sorts lists of mail messages by name. To compare two strings alphabetically, use the string<? primitive.
***
This exercise was pretty easy - the answer to most of this was given more or less directly in the previous example of sorting a list of numbers. To make this more challenging I tried to do this without referring back to the previous example.




First I defined a series of structs that contain some predefined emails. This is just to make to check-expects easier to code and to read.




; Number List-of-numbers -> List-of-numbers

(define-struct mail (from date message))
(define FIRST (make-mail "dave"  0 "hello"))
(define SECOND (make-mail "john" 10 "hello"))
(define THIRD (make-mail "andrew" 20 "hello"))
(define FOURTH (make-mail "andrew" 40 "hello"))


These check-expects are pretty similar to the previous example - I've just changed them to look at mails instead.


; List-of-mails -> List-of-mails
; produces a version of alom, sorted by date in descending order
(check-expect (sort-date-> empty) empty)
(check-expect (sort-date-> (list THIRD FOURTH FIRST)) 
              (list FOURTH THIRD FIRST))
(check-expect (sort-date-> (list THIRD SECOND FIRST)) 
              (list THIRD SECOND FIRST))
(check-expect (sort-date-> (list FIRST SECOND THIRD)) 
              (list THIRD SECOND FIRST))

Sort-date is again almost identical to the standard previous example. The only difference is that we are sorting by date and inserting by date.

(define (sort-date-> alom)
  (cond
    [(empty? alom) empty]
    [else (insert-date (first alom) (sort-date-> (rest alom)))]))



Here we insert our date in to the already sorted list. To simplify this I have made a helper function more-recent? which will compare the dates on two emails and let us know which is more recent.


; insert n into the sorted list of date alon
(define (insert-date n alon)
  (cond [(empty? alon) (cons n empty)]
        [else (cond [(more-recent? n (first alon)) (cons n alon)]
                    [else (cons (first alon) 
                                (insert-date n (rest alon)))])]))


This is the helper function to determine which of two emails is more recent. We could do this inline in the insert-date method, but this lends itself to more clarity. 

(define (more-recent? email-1 email-2)
  (>= (mail-date email-1) (mail-date email-2)))



The sort order is not specified, so I will assume we are sorting from most recent -> least recent                                

(check-expect (insert-date FIRST empty) (list FIRST))
(check-expect (insert-date FIRST (list SECOND )) 
              (list SECOND FIRST))
(check-expect (insert-date SECOND (list FIRST)) 
              (list SECOND FIRST))
(check-expect (insert-date SECOND (list THIRD FIRST)) 
              (list THIRD SECOND FIRST));;;




; For the second part of the question we need to sort by name. The easiest way to do this would be to pass the insert and comparison methods into our sort function so that the same function could sort both names and dates. However we haven't been told how to do this in the text yet, so I assume they don't want us to do this.


As this is so similar to sorting by date I won't document what I am doing in as much detail.


I have created some more structs for this - I could have rigged the original ones so that they were both alphabetically and date sorted, but creating new ones seems to be more honest.

(define NFIRST (make-mail "andrew"  20 "hello"))
(define NSECOND (make-mail "bob" 410 "hello"))
(define NTHIRD (make-mail "charlie" 20 "hello"))
(define NFOURTH (make-mail "dave" 4 "hello"))

; List-of-mails -> List-of-mails
; produces a version of alom, sorted by name in descending order
(check-expect (sort-name-> empty) empty)
(check-expect (sort-name-> (list NTHIRD NFOURTH NFIRST)) 
              (list NFOURTH NTHIRD NFIRST))
(check-expect (sort-name-> (list NTHIRD NSECOND NFIRST)) 
              (list NTHIRD NSECOND NFIRST))
(check-expect (sort-name-> (list NFIRST NSECOND NTHIRD)) 
              (list NTHIRD NSECOND NFIRST))

(define (sort-name-> alom)
  (cond
    [(empty? alom) empty]
    [else (insert-by-name (first alom) (sort-name-> (rest alom)) )]))

; insert n into the sorted list of mails alom
(define (insert-by-name n alom)
  (cond [(empty? alom) (cons n empty)]
        [else (cond [(alphabetically-earlier? n (first alom)) 
                     (cons n alom)]
                    [else (cons (first alom) 
                                (insert-by-name n (rest alom)))])]))

; sort order not specified, so will assume sort from most recent -> least recent                                
(check-expect (insert-by-name NFIRST empty) (list NFIRST))
(check-expect (insert-by-name NFIRST (list NSECOND ))
              (list NSECOaND NFIRST))
(check-expect (insert-by-name NSECOND (list NFIRST)) 
              (list NSECOND NFIRST))
(check-expect (insert-by-name NSECOND (list NTHIRD NFIRST))
              (list NTHIRD NSECOND NFIRST))

; utility function to determine which of two emails is more recent
(define (alphabetically-earlier? email-1 email-2)
  (string>? (mail-from email-1) (mail-from email-2)))


Thursday, 22 March 2012

Exercise 159: You know about first and rest from BSL, but BSL+ comes with even more selectors than that....


Exercise 159: You know about first and rest from BSL, but BSL+ comes with even more selectors than that. Determine the values of the following expressions:


  1. (first (list 1 2 3))
  2. (rest (list 1 2 3))
  3. (second (list 1 2 3))

Find out from the documentation whether third, fourth, and fifth exist.



This is a very straight forward exercise. As always with exercises of this type the hardest thing is making yourself do the exercise rather than just pasting the expressions into racket and seeing what they evaluate to.

I did not do as well as I expected - I got the first and third questions wrong. I was expecting them to return a list instead of the element.

A quick look at the documentation shows that not only do third, fourth and fifth exist, but so do sixth, seventh, eighth, nineth and tenth.

;(first (list 1 2 3))
(check-expect (first (list 1 2 3))
              1)


;(rest (list 1 2 3))
(check-expect (rest (list 1 2 3))
              (list 2 3))


;(second (list 1 2 3))
(check-expect (second (list 1 2 3))
              2)
     

Friday, 16 March 2012

Exercise 158: Determine the values of the following expressions...


Exercise 158: Determine the values of the following expressions:

  1. (list (string=? "a" "b") (string=? "c" "c") false)
  2. (list (+ 10 20) (* 10 20) (/ 10 20))
  3. (list "dana" "jane" "mary" "laura")

This is a pretty straight forward exercise. The hardest part here is to avoid the temptation to paste these directly in to the racket interpreter. I got 2/3 correct on my first pass though.


;1) (list (string=? "a" "b") (string=? "c" "c") false)
(check-expect (list (string=? "a" "b") (string=? "c" "c") false)
              (list false true false))
              
;2) (list (+ 10 20) (* 10 20) (/ 10 20))
(check-expect (list (+ 10 20) (* 10 20) (/ 10 20))
              (list 30 200 0.5))


;3) (list "dana" "jane" "mary" "laura")
(check-expect (list "dana" "jane" "mary" "laura")
              (list "dana" "jane" "mary" "laura"))