Overview
I would certainly appreciate suggestions of other extra credit activities (preferably via email).
Not questions about the exam. Those come later.
Example one
(list-ref (list-ref thing1 0) 1)
(list-ref (list-ref thing2 0) 1)
(list-ref (list-ref thing3 0) 1)
(list-ref (list-ref thing4 0) 1)
(list-ref (list-ref thing5 0) 1)
(define uh ; use helper
(lambda (thing)
(list-ref (list-ref thing 0) 1)))
(uh thing1)
(uh thing2)
(uh thing3)
(uh thing4)
(uh thing5)
Note: “uh” is funny, but not a particularly good name.
(regexp-match "<tag1>[^<]*</tag1>" (xml->string xml))
(regexp-match "<tag2>[^<]*</tag2>" (xml->string xml))
(regexp-match "<tag3>[^<]*</tag3>" (xml->string xml))
(regexp-match "<tag4>[^<]*</tag4>" (xml->string xml))
(regexp-match "<tag5>[^<]*</tag5>" (xml->string xml))
Let’s write a helper procedure
(define extract-tag
(lambda (pattern xml)
(regexp-match pattern (xml->string xml))))
(extract-tag "<tag1>[^<]*</tag1>")
(extract-tag "<tag2>[^<]*</tag2>")
(extract-tag "<tag3>[^<]*</tag3>")
(extract-tag "<tag4>[^<]*</tag4>")
(extract-tag "<tag5>[^<]*</tag5>")
Here’s a better version
(define extract-tag
(lambda (tag xml)
(regexp-match (string-append "<" tag ">[^<]*</" tag ">")
(xml->string xml))))
(extract-tag "tag1")
(extract-tag "tag2")
(extract-tag "tag3")
(extract-tag "tag4")
(extract-tag "tag5")
It probably works, but we might be able to do even better with a map
or for-each, e.g.,
(map extract-tag (list "tag1" "tag2" "tag3" "tag4" "tag5"))
Note: We are calling xml->string five times. It’s an expensive procedure.
We should avoid that.
We could write a let statement instead.
(define extract-tag
(lambda (tag xmlstr)
(regexp-match (string-append "<" tag ">[^<]*</" tag ">")
xmlstr)))
(let ([xmlstr (xml->string xml)])
(map (lambda (tag) (extract-tag tag xmlstr))
(list "tag1" "tag2" "tag3" "tag4" "tag5")))
Same number of lines, but more general and more efficient.
Moral: To avoid repetition,
Goal: To restate the purpose more formally, thereby avoiding ambiguity.
Postconditions for hash-invert
;;; Procedure:
;;; hash-invert
;;; Parameters:
;;; hash, a hash table
;;; Purpose:
;;; Invert the hash table
;;; Produces:
;;; inverted, a hash table
;;; Preconditions:
;;; [No additional]
;;; For any two keys, k1, k2, (hash-ref hash k1) is not equal to
;;; (hash-ref hash k2). That is, keys can't have duplicate values.
;;; Postconditions (with some repetition):
;;; * If hash is empty, inverted is also empty
;;; * For all keys, k, in hash:
;;; If (hash-ref hash k) = v, then (hash-ref inverted v) = k
;;; * For all keys, k, in hash:
;;; (hash-ref inverted (hash-ref hash k)) = k
;;; [another way to say the previous postcondition]
Suppose hash is #hash(("A" . "Apple") ("B" . "Banana")), what is
a hash table that meets the postconditions, but is not what you would
call an inverse of the original?
#hash(("Carrot" . "C") ("Apple" . "A") ("Banana" . "B"))
How could we write a postcondition that addresses that issue?
;;; * The number of keys in `inverted` must be the same as the
;;; number of keys in `hash`.
;;; (= (length (hash-keys inverted)) (length (hash-keys hash)))
How would you write the postconditions if we allowed two keys in
hash to have the same value?
#hash(("Apple" . "A") ("Aardvark" . "A") ("Banana" . "B")))
What is the inverse of that table?
#hash(("A" . "Apple") ("B" . "Banana"))
or
#hash(("A" . "Aardvark") ("B" . "Banana"))
;;; Postconditions:
;;; * If two or more keys in hash have the same value, then
;;; inverted will have a pair with that value as its key
;;; and one of those keys as its value. [Sam worries that this
;;; is incomplete.]
;;; * For all keys, k, in inverted:
;;; (hash-ref keys (hash-ref inverted k)) = k
;;; * If (hash-ref hash k) = v for some k, then v is a key of
;;; inverted.
;;; * inverted has no other keys.
Documenting sort-by-length
;;; Procedure:
;;; sort-by-length
;;; Parameters:
;;; strings, a list of strings [verified]
;;; Purpose:
;;; Sort strings from shortest to longest; if two strings have
;;; the same length, they should be in ASCII order.
;;; Produces:
;;; sorted, a list
;;; Preconditions:
;;; * strings must not be empty
;;; Postconditions:
;;; * (= (length sorted) (length strings))
;;; * If str2 appears immediately after str1 in sorted, then
;;; Either (string-length str1) < (string-length str2)
;;; Or the lengths are equal and (string-ci<=? str1 str2)
;;; * The elements in sorted are the same as the elements in strings.
SamR’s solution if we don’t have the third precondition
(define sort-by-length
(lambda (strings)
(make-list (length strings) "done")))
Note: We could replace the first and third postconditions with
;;; * sorted is a permutation of strings
(string-append "once upon a time " (generate-something "hello" "gooodbye") " there was a " (generate-noun) " ... ")
(string-append"hello"(generate)" and "(generate)) ; bad
(string-append "hello" (generate) " and " (generate)) ; good
(string-append "hello"
(generate)
" and "
(generate)) ; better
(list-ref
(list-ref
(some complex expression that was very very long longer) 1) 0) ; BAD
Names should be descriptive.
Follow Racket indentation guidelines. (Force Racket to reindent.)
How do you verify that everything in a list is a string?
(all string? strings)