In this homework you will have the chance to hone your recursive-thinking skills using Racket, in which recursion is the primary form of computational control. Racket is considered a very "clean" language, very consistent, and with minimal syntax: few punctuation and formatting rules. (Some might argue it's too minimal!) Regardless, Racket is one of the best examples of a language whose focus is supporting computation itself, as opposed to intricate data structuring. In this assignment you'll implement some useful and some silly but fun functions to familiarize yourself with Racket's lists, strings, and characters, but most of all to re-familiarize yourself with recursion!
This week, Part 1 should be in a file named hw1pr1.rkt and should be written by each person individually.
Part 2 may also be completed individually, but you are welcome -- and encouraged -- to work with a partner on those functions. If you do work in a pair, be sure to follow pair-programming practices as are detailed on the course syllabus. Either way, you should submit part two in a file named hw1pr2.rkt.
All of these are important! 25-30% of the assignment's points will be based on these. A few details:
Be sure to submit your hw1pr1.rkt and hw1pr2.rkt files to the submission site! If you pair-programmed in the second part, you need only submit one file (but do be sure to indicate your partner).
Start with this hw1pr1.rkt starter file, which helps by having the function signatures and the provided test cases already in place... .
Remember that these functions should be done individually. As always, you can seek out help from instructors, grutors, and fellow students, but the work submitted should be your own.
In class, you saw how (remove e L) works: it returns a list identical to
L, except with the first top-level instance of e removed. For this
problem, write (remove-all, which should return a list identical to
L, except that all top-level instances of e have been
removed. For example,
(Here we're using strings instead of symbols just to show that it will work either way... .)
(check-expect (remove-all "i" '("a" "l" "i" "i" "i" "e" "n")) '("a" "l" "e" "n"))
(check-expect (remove-all "i" '( ("a" "l" "i") "i" "i" "e" "n")) '(("a" "l" "i") "e" "n"))
(check-expect (remove-all 0 '(1 0 1 0 1 0)) '(1 1 1))
Write the Racket function that begins
in which P and L are both lists. Then, prefix? should
output #t if and only if the list P is identical to the
initial elements of the list L, i.e., P is the prefix of L.
You should consider the empty list as a prefix of any list L.
(define (prefix? P L)
Here are a couple of tests to help illustrate prefix?:
(check-expect (prefix? '() '(s p a m)) #t)
(check-expect (prefix? '(s p) '(s p a m)) #t)
(check-expect (prefix? '(s m) '(s p a m)) #f)
(check-expect (prefix? '(p a) '(s p a m)) #f)
Write the Racket function that begins
in which S and L are both lists. Then, sublist? should
output #t if and only if the list S is identical to the
some set of consecutive elements of the list L, i.e., S is a sublist of L.
You should consider the empty list to be a sublist of any list. However,
no non-empty list is a sublist of the empty list.
(define (sublist? S L)
Here are a couple of tests to help illustrate sublist?:
Hint: use prefix? to do most of the work here!!
(check-expect (sublist? '() '(s p a m)) #t)
(check-expect (sublist? '(s p) '(s p a m)) #t)
(check-expect (sublist? '(s m) '(s p a m)) #f)
(check-expect (sublist? '(p a) '(s p a m)) #t)
Run-time analysis: In a comment in your code, describe an example of a three-element list S and a six-element list L that require (sublist? S L) to do the most work (the most equality-comparisons). In other words, what inputs cause the largest number of equality-comparisons between elements to be made? Also, suppose that -- in general -- the inputs to sublist? are of length N and 2N, respectively. In this case, what is the resulting worst-case big-O running time of your sublist? function?
Write the Racket function that begins
in which L is a list. The output should be a list identical
to L, except that each element from the original input is
now the second element of a sequentially numbered list, starting from 0.
These examples
will help clarify this:
(define (number L)
Hint: create a helper function with more arguments than number.
Then, call the helper function with a reasonable default value!
(check-expect (number '(jan feb mar apr)) '((0 jan) (1 feb) (2 mar) (3 apr)))
(check-expect (number '(0 I II III IV V VI)) '((0 0) (1 I) (2 II) (3 III) (4 IV) (5 V) (6 VI)))
(check-expect (number '()) '())
Aside: Association lists, or "a-lists" for short, are always lists of lists. For example, the above function, number-each outputs association lists. Association lists are one way to store data values that can be looked up by a "key": here, the first element of each sublist is the key and the rest of each sublist is the corresponding value. We'll use association lists again in the scrabble-scoring functions, below.
For this part, start a new Racket file, named hw1pr2.rkt.
Be sure to include a top-of-file-comment after the
set-up lines:
#lang racket
(require htdp/testing)
Remember that these functions may be done individually or in a pair... .
Write the Racket function that begins
in which S and B are both lists. Then, subbag? should
output #t if and only if the list B contains all of the
elements in S in quantities at least as large. In contrast to
sublist?, however, the order of the
elements makes no difference. So, if S contains
three 42's, then B would have to contain at least three 42's
in order for S to be a subbag of B. (By the way, a bag is
a methematical term for a set in which there may be more than one of the same kind of
element.)
(define (subbag? S B)
Here are a couple of tests to help illustrate subbag?:
(check-expect (subbag? '() '(s p a m s)) #t)
(check-expect (subbag? '(s s) '(s p a m s)) #t)
(check-expect (subbag? '(s m) '(s p a m s)) #t)
(check-expect (subbag? '(p a) '(s p a m s)) #t)
(check-expect (subbag? '(a m a) '(s p a m s)) #f)
Write a function that begins
superreverse will be a variant of reverse. Its input L
will be a list that will only contain lists as elements.
superreverse should output a list identical to L, except that
all of its top-level lists should be reversed. No deeper structures should be
altered. You may (and should!) use the built-in reverse function in your
implementation of superreverse.
(define (superreverse L)
Here are two examples. In the first, we use Racket's single-character
datatype, just to emphasize that it differs from both symbols 'symbol
and strings "string".
(check-expect (superreverse '( (1 2 3) (4 5 6) (#\k #\o #\o #\l) (#\a #\m) ))
'( (3 2 1) (6 5 4) (#\l #\o #\o #\k) (#\m #\a) ) )
(check-expect (superreverse '( (1 2 3) (4 5 6 (7 8) 9 ) ))
'( (3 2 1) (9 (7 8) 6 5 4) ) )
Next, define a function
which returns a list whose structure is a complete reversal
of L's. Note that L does not need to have only
lists as elements!
(define (duperreverse L)
Remember that you can use the Racket predicate (list? x)
to determine whether or not x is a list!
You might use the memAny example from class as a guide... .
(check-expect (duperreverse '( (1 2 3) (4 5 6) 42 ("k" "o" "o" "l") ("a" "m") ))
'( ("m" "a") ("l" "o" "o" "k") 42 (6 5 4) (3 2 1) ) )
(check-expect (duperreverse '( (1 2 3) (4 5 6 (7 8) 9 ) ))
'( (9 (8 7) 6 5 4) (3 2 1) ) )
Write a tail-recursive function
that takes in a nonnegative
integer N and outputs the Nth
fibonacci number. The fibonacci numbers are the series that
begins 1 1 and then forms each subsequent element
by adding the last two:
(define (tail-fib N)
For the purposes of this problem, the initial
Fibonacci number will correspond to an input N of 0.
As a refrence, here is a non-tail-recursive version
of fib:
Fibonacci numbers: 1 1 2 3 5 8 13 21 ...
Their indices, N: 0 1 2 3 4 5 6 7 ... (for this problem)
(define (fib N)
(if (< N 2)
1
(+ (fib (- N 1)) (fib (- N 2)))
))
Here is an example test for tail-fib:
Note that the class's examples called the helper function
tail-range, tail-power,
etc. Here, we're using tail-fib as the name of the
1-input function. (I guess you'll get to choose a more
creative helper-function name!)
(check-expect (tail-fib 8) 34)
Hint! For tail-fib it will be simplest to create a helper function that has two accumulator arguments! You might start them out at 1 and 1... .
In a comment near your tail-fib,
estimate the big-O running time of (tail-fib N)
in terms of N, the value of the input.
Using the built-in time function, include
a comment that tells the smallest value of N
for which (time (tail-fib N)) takes over 1
second to run, using the first output number that time
provides. You can round N to the
nearest thousand, if you like. Also, indicate the smallest
value of N for which (time (fib N))
takes over 1 second of time to run. (But this latter one should
be the exact value of N: it'll be much smaller)
The cpu-time
is the first value that time reports, in milliseconds.
In order to avoid seeing the (possible very large) value that
is returned by the function call, you can use the following
to do your timing:
In Racket, begin simply returns the last one of the
expressions it's given as input. But all the printing is
still done along the way, so you'll see the results of time.
(begin (time (tail-fib 50000)) '())
(define (tail-reverse L)
that takes in a list L and returns the reverse of that list.
For example,
(check-expect (tail-reverse '(6 7 1 2)) '(2 1 7 6))
In a comment near this function, estimate the big-O runtime
of your tail-reverse and of the "ordinary" (non-tail-recursive)
reverse here:
In analyzing this "ordinary" reverse, use the fact that
append runs in time O(N), where N is the length of
the first input to append.
(This is counting cons.)
(define (reverse L)
(if (null? L)
'()
(append (reverse (rest L)) (list (first L)))
))
Overview: This problem asks you to
implement a set of Racket functions that computes the
highest-scoring (or "best") Scrabble word, given
a particular rack of letters rack and a list of legal words WL.
Here would be the definition line:
The output of best-word should be a two-element
list in which the first element is the best word and the
second element is the score of that best word. If there
are ties, best-word may return any one of the best.
(define (best-word rack WL) ;; rack is a string; WL is a list of strings
Here are a few examples:
Note that "paper" could not be made in the third example, because the rack had
only a single #\p.
(check-expect (best-word "academy" '("ace" "ade" "cad" "cay" "day")) '("cay" 8))
(check-expect (best-word "appler" (list "peal" "peel" "ape" "paper")) '("paper" 9))
(check-expect (best-word "paler" (list "peal" "peel" "ape" "paper")) '("peal" 6))
In general, there may be more than one best-scoring word -- in that case,
we will make sure to run tests similar to the following:
In this case, either "cay" or "bay" would produce that best score.
(check-expect (second (best-word "bcademy" '("ace" "ade" "cad" "cay" "bay"))) 8)
In this problem we will use an association-list to score information about scrabble
tiles. The following Scheme statement defines an association-list
that provides all of the letter
scores. It also includes the number of times each tile appears
in an official Scrabble set -- this is only for the optional extra-credit
problem. You should copy this code into your hw1pr2.rkt file:
The notation #\a is Scheme's way of representing single-character literals.
;; scrabble-tile-bag
;; letter tile scores and counts from the game of Scrabble
;; the counts aren't needed (until the ex. cr.) they're obtained from
;; http://en.wikipedia.org/wiki/Image:Scrabble_tiles_en.jpg
;;
(define scrabble-tile-bag
'((#\a 1 9) (#\b 3 2) (#\c 3 2) (#\d 2 4) (#\e 1 12)
(#\f 4 2) (#\g 2 3) (#\h 4 2) (#\i 1 9) (#\j 8 1)
(#\k 5 1) (#\l 1 4) (#\m 3 2) (#\n 1 6) (#\o 1 8)
(#\p 3 2) (#\q 10 1)(#\r 1 6) (#\s 1 4) (#\t 1 6)
(#\u 1 4) (#\v 4 2) (#\w 4 2) (#\x 8 1) (#\y 4 2)
(#\z 10 1) (#\_ 0 2)) )
;; end define scrabble-tile-bag
;;
;; The underscore will be used to represent a blank tile, which is a wild-card
The function (assoc e A) takes in an association-list A (remember that
A contains lists of lists) and returns the element of A whose own
first element is equal to e. For example:
(assoc 3 '((0 jan) (1 feb) (2 mar) (3 apr))) ==> (3 apr) (assoc 5 '((1 jan) (2 feb) (3 mar) (4 apr))) ==> #fAs the second example shows, assoc returns #f when e does not appear as a first element of any of A's sublists. This assoc function will help look up letters and their scores.
score-letter
As a possible helper function, you might
construct a function named score-letter that determines
the score for its argument, which will be a character. Use assoc.
For example,
(check-expect (score-letter '#\w) 4)
score-word
As another possible helper, you might
construct a function score-word that determines the score
for a string of letters. Note that strings in Scheme are not the same as
symbols, nor are characters the same as strings (as they are in Python).
Strings are
doubly quoted. The built-in functions string->list and
list->string, which convert from strings to lists of characters and
vice-versa, may be of use. Here are examples of them in action:
(check-expect (string->list "hi") '(#\h #\i))
(check-expect (list->string '(#\h #\i)) "hi")
Here are three tests of score-word:
(check-expect (score-word "zzz") 30)
(check-expect (score-word "fortytwo") 17)
(check-epxect (score-word "twelve") 12)
Other hints Be sure to plan out your strategy for this problem before diving in to the coding! In our solution, we used the subbag? predicate from earlier in this assignment... .
(define (hard-scrabble rack WL)
As with best-word, hard-scrabble should return
a 2-element list including the best makable word and its score.
In this case, however, it should allow the use of wild-card "blank"
characters, which can act as any letter, and it should check
that the rack does not have more of a particular letter
than are available in the game! We will use #\_, the
underscore character, to represent the blank.
If the rack is impossible, the output
should be '("no cheating!" 0). The blanks do not have to
appear in the output word, but they should be accounted for.
(check-expect (hard-scrabble "iazby_q" '("biz" "jazz" "qi" "quiz"))
'("quiz" 21)) ;
(check-expect (hard-scrabble "iazzjaq" '("biz" "jazz" "qi" "quiz"))
'("no cheating! 0))
For this extra-credit problem, we will use an association list as a
graph, a general-purpose representation of relationships
by connectivity. For example, the association list
would represent this graph:
(define Graph1 '( (a b) (b c) (c d) (d e) (e c) ))

In particular, this challenge is to implement the predicate reachable?:
in which x and y are nodes in the graph, G.
Then, reachable? should return #t if there is a path,
perhaps through some number of intermediate nodes, from node x
to node y within graph G. If there is no such path
from x to y, the predicate reachable? should
return #f. We consider any node reachable from itself, even if
it's not in the graph.
(define (reachable? x y G)
Here are a couple of examples that use Graph1:
(check-expect (reachable? 'a 'a Graph1) #t)
(check-expect (reachable? 'z 'z Graph1) #t)
(check-expect (reachable? 'a 'b Graph1) #t)
(check-expect (reachable? 'a 'e Graph1) #t)
(check-expect (reachable? 'e 'd Graph1) #t)
(check-expect (reachable? ''e 'a Graph1) #f)