Recall that all Racket programs are just lists. By allowing Racket programs to be transformed at compile time, we can reduce code duplication. Racket programs that transform other Racket programs at compile time are called macros.
Consider the following expressions with higher-order functions:
> (map (lambda (x) (+ x 1)) '(1 2 3 4 5)) '(2 3 4 5 6) > (map (lambda (x) (* x 2)) '(1 2 3 4 5)) '(2 4 6 8 10)These expressions each apply a function to every element in a list, generating a new list. But one could argue that the syntax could be cleaned up considerably. Consider the following alternative:
> ($ (x) '(1 2 3 4 5) (+ x 1)) '(2 3 4 5 6) > ($ (x) '(1 2 3 4 5) (* x 2)) '(2 4 6 8 10)The syntax here says, "For each
x
drawn from the list, perform the specified computation." This is an example of a list comprehension. In Racket, we can write a macro to implement this syntax as follows:
(define-syntax-rule ($ (x) lst expr) (map (lambda (x) expr) lst))We can enhance the macro as follows. Let's say we only want to include mappings of values that meet a certain criterion. In this example, we want to multiply positive numbers by 2, but ignore the negative numbers:
> ($ (x) '(1 -2 -3 4 -5) (* x 2) (> x 0)) '(2 8)We could write a macro for this variation as follows:
(define-syntax-rule ($ (x) lst expr p) (map (lambda (x) expr) (filter (lambda (x) p) lst)))Racket allows us to combine multiple macros bound to the same keyword (with differing numbers of parameters) as follows:
(define-syntax $ (syntax-rules () [($ (x) lst expr) (map (lambda (x) expr) lst)] [($ (x) lst expr p) (map (lambda (x) expr) (filter (lambda (x) p) lst))]))
(define (fib n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))The trouble with this implementation is that certain recursive calls, with the same results, are repeated an excessive number of times.
The trouble with this solution is that the implementation of the function becomes polluted with the need to interact with the hash table:
(define (fib-hash n) (let ((fibs (make-hash))) (letrec ((fib-recur (lambda (n) (if (hash-has-key? fibs n) (hash-ref fibs n) (let ((value (if (<= n 2) 1 (+ (fib-recur (- n 1)) (fib-recur (- n 2)))))) (hash-set! fibs n value) value))))) (fib-recur n))))What we would ideally like to do would be to define the function in the naive manner, but with a flag indicating that it is to be memoized:
(define-memo (fib n) (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2)))))We can achieve this by creating a
define-memo
macro.
build-list
. Here are some examples of what it should do:
> (^ (x) 5 x) '(0 1 2 3 4) > (^ (x) 5 (* 2 x)) '(0 2 4 6 8) > (^ (x) 1 5 (* 2 x)) '(2 4 6 8 10) > (^ (x) 1 5 2 (* 2 x)) '(2 6 10) > (^ (x) 5 1 -2 (* 2 x)) '(10 6 2) > (^ (x) 1 10 2 x) '(1 3 5 7 9)
&<
) will be a left fold; the second one
(&>
) will be a right fold. Both of them will have an optional initial value for the fold. If no initial value is provided, use the car
of the list. Here are some examples:
> (&< (x y) '(1 2 3 4) (* x y)) 24 > (&< (x y) '(1 2 3 4) 1 (* x y)) 24 > (&< (x y) '(1 2 3 4) '() (cons x y)) '(4 3 2 1) > (&> (x y) '(1 2 3 4) '() (cons x y)) '(1 2 3 4)
define-memo
such that the third
version of fib
has the run-time performance of the second version.
n
for which the first version
of fib
either crashes Racket, or takes at least 10 seconds to complete. Then verify that the memoized version produced by your macro works properly.
pascal(0, 0) = pascal(0, k) = pascal(k, k) = 1
pascal(n, k) = pascal (n-1, k-1) + pascal (n-1, k)
i
and capacity up to w
:
max-value(0, w) = 0
max-value(i, 0) = 0
max-value(i, w) = max-value(i-1, w)
, if wi > w
max-value(i, w) = max(max-value(i-1, w), vi + max-value(i-1, w - wi))
, if wi ≤ w