• Dueish: Tue Mar 5.

  • Notes:
    • This pset has a total of 105 points.
    • This pset contains a solo problem worth 25 points with several subproblems
    • You have seen the material for Problems 1 and 2; the material for Problem 3 was covered in the First-Class Functions slides on Mon Feb 25; and the material for Probelms 4 and 5 is covered in the Higher-Order List Functions Slides from Wed Feb 27 and Thu Feb 28.
    • The problems needn’t be done in order. Feel free to jump around.
  • Times from Spring ‘18

    Times Problem 1 Problem 2 Problem 3 Problem 4 Problem 5 Total
    average time (hours) 1.9 1.6 3.1 1.4 0.9 8.0
    median time (hours) 1.8 1.5 2.8 1.2 0.7 8.3
    25% took more than 2.0 2.0 4.0 1.8 1 10
    10% took more than 3.1 2.6 4.4 2.1 1.5 10.5
  • Submission:
    • In your yourFullName CS251 Spring 2019 Folder, create a Google Doc named yourFullName CS251 PS4.
    • At the top of your yourFullName CS251 PS4 doc, include your name, problem set number, date of submission, and an approximation of how long each problem part took.
    • When a problem is ready to be graded, put DONE next to the problem label at the top of the document
    • For all parts of all problems, include all answers (derviations, code, etc.) in your PS4 google doc. Please format your evaluation derivations so that they’re easy to read. Format small-step derivations and Racket code using a fixed-width font, like Consolas or Courier New. You can use a small font size if that helps.
    • For Problem 1 (Solo Problem: Recursive List Functions)
      • Be sure that all function definitions in yourAccountName-ps4-solo-functions.rkt also appear in your Google Doc (so that I can comment on them)
      • Drop a copy of your yourAccountName-ps4-solo-functions.rkt in your ~/cs251/drop/ps04 drop folder on cs.wellesley.edu.
    • For Problems 3 through 5:
      • Be sure that all function definitions in yourAccountName-ps4-functions.rkt also appear in your Google Doc (so that I can comment on them)
      • Drop a copy of your yourAccountName-ps4-functions.rkt in your ~/cs251/drop/ps04 drop folder on cs.wellesley.edu.

1. Solo Problem: Recursive List Functions (25 points)

This is a solo problem. This means you must complete it entirely on your own without help from any other person and without consulting resources other than course materials or online documentation. You may ask Lyn for clarification, but not for help.

In this problem you will define three recursive functions on lists. Some ground rules:

  • Define all of your functions in a new file named yourAccountName-ps4-solo-functions.rkt that you create in Dr. Racket.
  • You should use explicit recursion on lists in all of your definitions.
  • In all of your definitions, you should use the divide/conquer/glue strategy taught in class that you practiced in PS3.
  • You should not use any higher-order list operations (e.g., map, filter, foldr, foldl, iterate, or genlist) in this problem.
  • The only built-in Racket list operators you may use in your definitions are: null, null?, cons, list, append, first, second, third, and rest. (You may also use any Racket math or logic operators, such as +, max, etc.)
  • You should use Racket’s let construct to avoid evaluating an expression more than once.
  • In this problem, you should define and use the following prob1-map-cons helper function:

    (define (prob1-map-cons x yss)
      (if (null? yss)
          null
          (cons (cons x (first yss)) (prob1-map-cons x (rest yss)))))

    (This has the special name prob1-map-cons so as not to conflict with the map-cons function you will define in Problem 3.)

  • Other than prob1-map-cons, you should not use any other helper functions in this problem except for the one helper function specified in 1d weighted-suffixes.
  1. (5 points) A length-n prefix of a list is a list containing its first n elements in the same relative order. For example:

    • The length-0 prefix of '(5 8 4) is '()
    • The length-1 prefix of '(5 8 4) is '(5)
    • The length-2 prefix of '(5 8 4) is '(5 8)
    • The length-3 prefix of '(5 8 4) is '(5 8 4)

    Define a function prefixes that takes a list as its single argument and returns a list of all of its prefixes ordered from shortest to longest. For example:

     > (prefixes '(5 8 4))
     '(() (5) (5 8) (5 8 4))
     > (prefixes '(2 5 8 4))
     '(() (2) (2 5) (2 5 8) (2 5 8 4))
     > (prefixes '(7 2 5 8 4))
     '(() (7) (7 2) (7 2 5) (7 2 5 8) (7 2 5 8 4))
     > (prefixes (range 0 11))
     '(()
       (0)
       (0 1)
       (0 1 2)
       (0 1 2 3)
       (0 1 2 3 4)
       (0 1 2 3 4 5)
       (0 1 2 3 4 5 6)
       (0 1 2 3 4 5 6 7)
       (0 1 2 3 4 5 6 7 8)
       (0 1 2 3 4 5 6 7 8 9)
       (0 1 2 3 4 5 6 7 8 9 10))
  2. (8 points) Define a function sum-max-squaresEvens that takes a list of integers its single argument and returns a triple (i.e., a three-element list) whose three elements are (1) the sum of the numbers in the list; (2) the maximum of the numbers in the list and (3) a list of the squares of all the even numbers in the list (maintaining relative order).

     > (sum-max-squaresEvens '(9 2 8 5 4 7 1 6 3))
     '(45 9.0 (4 64 16 36))
     > (sum-max-squaresEvens '(2 8 5 4 7 1 6 3))
     '(36 8.0 (4 64 16 36))
     > (sum-max-squaresEvens '(8 5 4 7 1 6 3))
     '(34 8.0 (64 16 36))
     > (sum-max-squaresEvens '(5 4 7 1 6 3))
     '(26 7.0 (16 36))
     > (sum-max-squaresEvens '(-9 2 -8 5 4 -7 1 -6 3))
     '(-15 5.0 (4 64 16 36))
     > (sum-max-squaresEvens '(-6 -3 -10 -5 -8))
     '(-32 -3.0 (36 100 64))
     > (sum-max-squaresEvens (append (range 1 101 7) (range 201 0 -2)))
     '(10951 201.0 (64 484 1296 2500 4096 6084 8464))

    Your sum-max-squaresEvens function should make a single pass over the input list to produce the output triple. I.e., you should not have separate recursions for calculating each of the three parts.

  3. (5 points) Suppose that we represent a set in Racket as a list without duplicates. Define a function subsets that takes as its single argument a set and returns a list of all subsets of a given set. The subsets within the result list can be in any order, but the order of elements within each set should have the same relative order as in set.

    For example here are some of the (huge number of) possible answers for (subsets '(3 1 2)), any single one of which would be considered correct:

    '(() (1) (2) (3) (1 2) (3 1) (3 2) (3 1 2))
    '((3 1 2) (3 2) (3 1) (1 2) (3) (2) (1) ())
    '(() (2) (1) (1 2) (3) (3 2) (3 1) (3 1 2))  
    '((3 1 2) () (3 1) (2) (3) (1 2) (1) (3 2))

    However, lists containing subsets like (2 1), (1 3), (3 2 1), or (1 2 3) could not be solutions, since the elements of these subsets are not in the same relative order as in (3 1 2).

  4. (7 points) A length-n suffix of a list is a list containing its last n elements in the same relative order. For example:

    • The length-0 suffix of '(5 8 4) is '()
    • The length-1 suffix of '(5 8 4) is '(4)
    • The length-2 suffix of '(5 8 4) is '(8 4)
    • The length-3 suffix of '(5 8 4) is '(5 8 4)

    Based on this definition, imagine a function suffixes that takes a list as its single argument and returns a list of all of its suffixes ordered from longest to shortest. For example:

     > (suffixes '(5 8 4))
     '((5 8 4) (8 4) (4) ())  
     > (suffixes '(2 5 8 4))
     '((2 5 8 4) (5 8 4) (8 4) (4) ())
     > (suffixes '(7 2 5 8 4))
     '((7 2 5 8 4) (2 5 8 4) (5 8 4) (8 4) (4) ())
     > (suffixes (range 1 11))
     '((1 2 3 4 5 6 7 8 9 10)
       (2 3 4 5 6 7 8 9 10)
       (3 4 5 6 7 8 9 10)
       (4 5 6 7 8 9 10)
       (5 6 7 8 9 10)
       (6 7 8 9 10)
       (7 8 9 10)
       (8 9 10)
       (9 10)
       (10)
       ())

    In this problem, you are not asked to define suffixes, but are instead asked to define a related function named weighted-suffixes, which is assumed to take a list of numbers. The result of weighted-suffixes is a list similar to that returned by suffixes except that each nonempty sublist in the result of weighted-suffixes is the result of scaling all numbers in the corresponding nonempty sublist in the result of suffixes by its first element. (The empty sublist in suffixes yields the empty sublist in weighted-suffixes).

    For example, (weighted-suffixes '(7 2 5 8 4)) returns '((49 14 35 56 28) (4 10 16 8) (25 40 20) (64 32) (16) ()) because:

    • (49 14 35 56 28) is the result of scaling (7 2 5 8 4) by 7
    • (4 10 16 8) is the result of scaling (2 5 8 4) by 2
    • (25 40 20) is the result of scaling (5 8 4) by 5
    • (64 32) is the result of scaling (8 4) by 8
    • (16) is the result of scaling (4) by 4
    • () is the sublist in the result of weighted-suffixes that corresponds to the sublist () in the result of suffixes

    Here are more examples of weighted-suffixes, the last two of which illustrate negative numbers:

     > (weighted-suffixes (range 3 8))
     '((9 12 15 18 21) (16 20 24 28) (25 30 35) (36 42) (49) ())
    
     > (weighted-suffixes (range 1 11))
     '((1 2 3 4 5 6 7 8 9 10)
       (4 6 8 10 12 14 16 18 20)
       (9 12 15 18 21 24 27 30)
       (16 20 24 28 32 36 40)
       (25 30 35 40 45 50)
       (36 42 48 54 60)
       (49 56 63 70)
       (64 72 80)
       (81 90)
       (100)
       ())
        
     > (weighted-suffixes '(-2 6 1 -3 -8 4 7 -5))
     '((4 -12 -2 6 16 -8 -14 10)
       (36 6 -18 -48 24 42 -30)
       (1 -3 -8 4 7 -5)
       (9 24 -12 -21 15)
       (64 -32 -56 40)
       (16 28 -20)
       (49 -35)
       (25)
       ())
    
     > (weighted-suffixes (range -3 4))
     '((9 6 3 0 -3 -6 -9) (4 2 0 -2 -4 -6) (1 0 -1 -2 -3) (0 0 0 0) (1 2 3) (4 6) (9) ())

    Recall that you cannot use the higher-order map function here. So, in this problem, in addition to weighted-suffixes, you will need to define a recursive list helper function that implements the mapping pattern to scale all elements in a list of numbers by a given scaling factor.

2. Wacky Lists (15 points)

This problem shows that functions can be used to implement data structures like pairs and lists. Consider the following alternatives to Racket’s usual built-in cons, car, cdr, null, and null?:

    (define kons (λ (x y) (λ (s) (s #f x y))))

    (define kar (λ (k) (k (λ (b l r) l))))

    (define kdr (λ (k) (k (λ (b l r) r))))

    (define knil (λ (s) (s #t 0 0)))

    (define knil? (λ (k) (k (λ (b l r) b))))
  1. (2 points) Use the small-step substitution model (i.e., using ⇒) to show the evaluation of the value bound to the name p by the following declaration:

     (define p (kons 3 4))

    Show every step; do not abbreviate steps via ⇒*. In each step, explicitly (1) show the redex (in curly braces) and (2) give the name of the rule applied (in square brackets).

  2. (9 points) Use the small-step substitution model (i.e., using ⇒) to show the evalulation of each of the following expressions. In these steps, you should use an environment env containing that contains the binding p↦λ_p, where λ_p is your value from part a. You should assume env also contains bindings for kons, kar, kdr, knil, and knil?.
    • (kar p)
    • (kdr p)
    • (knil? p)
    • (knil? knil)

    Show every step; do not abbreviate steps via ⇒*. In each step, explicitly (1) show the redex (in curly braces) and (2) give the name of the rule applied (in square brackets). Carefully review each step to make sure that it makes sense. If you’re not sure, consult Lyn or the tutors for help.

  3. (2 points) The following sum-to function uses helper functions sum and down-from defined in terms of the list-like entities involving kons and friends. Does it actually calculate the sum of the integers from 1 to n (inclusive)? Explain why or why not.

     (define (sum-to n)
       (sum (down-from n)))
    
     (define (sum nums)
       (if (knil? nums)
           0
           (+ (kar nums) (sum (kdr nums))))) 
    
     (define (down-from n)
       (if (<= n 0)
           knil
           (kons n (down-from (- n 1)))))
  4. (2 points) Can we replace all instances of cons/car/cdr/null/null? in Racket by kons/kar/kdr/knil/knil?? Are there any ways in which kons/kar/kdr/knil/knil? do not behave like cons/car/cdr/null/null?. Some things to think about in this context:

    • What is the value of (car null)?(kar knil)`?
    • Can cons and friends interoperate with kons and friends?

3. Higher-order List Functions (35 points)

In this and the following problems you will revisit some functions from PS3 Problem 3, as well as see some new ones. However, rather than expressing them as recursions, you will express them in terms of higher-order-list-operators.

Notes:

  • For Problems 3 through 5, you should use Dr. Racket to create a single file named yourAccountName-ps4-functions.rkt that contains all the functions (including helper functions) that you define for these problems.

  • In your definitions, you are not allowed to use recursion anywhere. (The one exception is the inserts-rec helper function you are given in Problem 3l.)

  • In your definitions, unless otherwise instructed, you should not introduce any new named helper functions, but you can (1) liberally use anonymous functions and (2) use functions you defined in previous parts in later parts.

  1. (2 points) Using Racket’s map, define a function map-remainder that takes two arguments (an integer divisor and a list ints of integers) and returns an integer list the same length as ints in which every element is remainder of dividing the corresponding element of ints by divisor.

     > (map-remainder 2 '(16 23 42 57 64 100))
     '(0 1 0 1 0 0)
     > (map-remainder 3 '(16 23 42 57 64 100))
     '(1 2 0 0 1 1)
     > (map-remainder 5 '(16 23 42 57 64 100))
     '(1 3 2 2 4 0)
     > (map-remainder 17 '(16 23 42 57 64 100))
     '(16 6 8 6 13 15)
  2. (2 points) Using Racket’s filter, define a function filter-divisible-by that takes two arguments (an integer divisor and a list ints of integers) and returns a new integer list containing all the elements of ints that are divisible by divisor.

     > (filter-divisible-by 2 '(16 23 42 57 64 100))
     '(16 42 64 100)
     > (filter-divisible-by 3 '(16 23 42 57 64 100))
     '(42 57)
     > (filter-divisible-by 4 '(16 23 42 57 64 100))
     '(16 64 100)
     > (filter-divisible-by 5 '(16 23 42 57 64 100))
     '(100)
     > (filter-divisible-by 17 '(16 23 42 57 64 100))
     '()

    Use the following helper function, which is helpful in this problem and some of the following ones.

    (define divisible-by?
      (lambda (num divisor)
        (= (remainder num divisor) 0)))
  3. (3 points) Using Racket’s foldr, define a function contains-multiple? that takes an integer m and a list of integers ns that returns #t if m evenly divides at least one element of the integer list ns; otherwise it returns #f. Use divisible-by? from above to determine divisibility.

     > (contains-multiple? 5 '(8 10 14))
     #t
     > (contains-multiple? 3 '(8 10 14))
     #f
     > (contains-multiple? 5 '())
     #f
  4. (3 points) Using Racket’s foldr, define a function all-contain-multiple? that takes an integer n and a list of lists of integers nss (pronounced “enziz”) and returns #t if each list of integers in nss contains at least one integer that is a multiple of n; otherwise it returns #f. Use contains-multiple? in your definition of all-contain-multiple?.

     > (all-contain-multiple? 5 '((17 10 2) (25) (3 8 5)))
     #t
     > (all-contain-multiple? 2 '((17 10 2) (25) (3 8 5)))
     #f
     > (all-contain-multiple? 3 '())
     #t ; said to be "vacuously true"; there is no counterexample!
  5. (2 points) Using Racket’s foldr, define a function snoc that takes a value x and a list ys and returns the new list that results from adding x to the end of ys.

     > (snoc 4 '(7 2 5))
     '(7 2 5 4)
     > (snoc 4 '())
     '(4)
  6. (2 points) Using Racket’s foldr, define a function my-append that takes two lists, xs and ys, and returns the new list that contains all the elements of xs followed by all the elements of ys.

     > (my-append '(7 2 5) '(4 6))
     '(7 2 5 4 6)
     > (my-append '() '(4 6))
     '(4 6)
     > (my-append '(7 2 5) '())
     '(7 2 5)
     > (my-append '() '())
     '()

    Note: You may not use Racket’s append in your definition.

  7. (2 points) Using Racket’s foldr, define a function append-all that takes a list of lists xss and returns a new list that contains all the elements of the sublists of xss in their relative order.

     > (append-all '((1 2) (3) (4 5 6)))
     '(1 2 3 4 5 6)
     > (append-all '((1 2) (3)))
     '(1 2 3)
     > (append-all '((1 2)))
     '(1 2)
     > (append-all '())
     '()
     > (append-all '(((1 2) (3 4 5)) ((6)) ((7 8) () (9))))
     '((1 2) (3 4 5) (6) (7 8) () (9))

    Note: You may use append or my-append in your definition.

  8. (2 points) Using Racket’s map, define a function map-cons that takes any value x and an n-element list ys and returns an n-element list of all pairs '(x . y) where y ranges over the elements of ys. The pair '(x . y) should have the same relative position in the resulting list as y has in ys.

     > (map-cons 17 '(8 5 42 23))
     '((17 . 8) (17 . 5) (17 . 42) (17 . 23))
     > (map-cons 3 '((1 6 2) (4 5) () (9 6 8 7)))
     '((3 1 6 2) (3 4 5) (3) (3 9 6 8 7))
     > (map-cons 42 '())
     '()
  9. (3 points) Using Racket’s foldr, define a function my-cartesian-product that takes two lists xs and ys and returns a list of all pairs '(x . y) where x ranges over the elements of xs and y ranges over the elements of ys. The pairs should be sorted first by the x entry (relative to the order in xs) and then by the y entry (relative to the order in ys).

     > (my-cartesian-product '(1 2) '("a" "b" "c")) 
     '((1 . "a") (1 . "b") (1 . "c") (2 . "a") (2 . "b") (2 . "c"))
     > (my-cartesian-product '(2 1) '("a" "b" "c"))
     '((2 . "a") (2 . "b") (2 . "c") (1 . "a") (1 . "b") (1 . "c"))
     > (my-cartesian-product '("c" "b" "a") '(2 1))
     '(("c" . 2) ("c" . 1) ("b" . 2) ("b" . 1) ("a" . 2) ("a" . 1))
     > (my-cartesian-product '("a" "b") '(2 1))
     '(("a" . 2) ("a" . 1) ("b" . 2) ("b" . 1))
     > (my-cartesian-product '(1) '("a"))
     '((1 . "a"))
     > (my-cartesian-product '() '("a" "b" "c"))
     '()

    Note: You may use map-cons and append or my-append in your definition.

  10. (2 points) Using Racket’s foldr, define a function my-reverse that takes a list xs and returns a new list whose elements are the elements of xs in reverse order. You may not use the built-in reverse function.

    > (my-reverse '(1 2 3 4))
    '(4 3 2 1)
    > (my-reverse '(1))
    '(1)
    > (my-reverse '())
    '()

    Note:

    • We ask you to name your function my-reverse because Racket already provides the same function named reverse (which you cannot use, of course).
    • You may use snoc or append or my-append in your definition.
  11. (3 points) Assume that the elements of a list are indexed starting with 0. Using Racket’s foldr, define a function alts that takes a list xs and returns a two-element list of of lists, the first of which has all the even-indexed elements (in the same relative order as in xs) and the second of which has all the odd-indexed elements (in the same relative order as in xs).

    > (alts '(7 5 4 6 9 2 8 3))
    '((7 4 9 8) (5 6 2 3))
    > (alts '(5 4 6 9 2 8 3))
    '((5 6 2 3) (4 9 8))
    > (alts '(4 6 9 2 8 3))
    '((4 9 8) (6 2 3))
    > (alts '(3))
    '((3) ())
    > (alts '())
    '(() ())

    Note: There is no need to treat even-length and odd-length cases differently, nor is there any need to treat the singleton list specially.

  12. (5 points) Using Racket’s foldr, define a function inserts-foldr that takes a value x and an n-element list ys and returns an n+1-element list of lists showing all ways to insert a single copy of x into ys.

     > (inserts-foldr 3 '(5 7 1))
     '((3 5 7 1) (5 3 7 1) (5 7 3 1) (5 7 1 3))
     > (inserts-foldr 3 '(7 1))
     '((3 7 1) (7 3 1) ( 7 1 3))
     > (inserts-foldr 3 '(1))
     '((3 1) (1 3))
     > (inserts-foldr 3 '())
     '((3))
     > (inserts-foldr 3 '(5 3 1))
     '((3 5 3 1) (5 3 3 1) (5 3 3 1) (5 3 1 3))

    Notes:

    • The map-cons function from above is useful here.
    • Think very carefully about the base case and the combination function for the recursive case.
    • Your definition should have exactly this pattern:

      (define (inserts-foldr x ys)
        (foldr ; binary combiner goes here
               ; null value goes here
               ys))
  13. (4 points) Using Racket’s foldr, define a function my-permutations that takes as its single argument a list xs of distinct elements (i.e., no duplicates) and returns a list of all the permutations of the elements of xs. The order of the permutations does not matter.

    > (my-permutations '())
    '(())
    > (my-permutations '(4))
    '((4))
    > (my-permutations '(3 4))
    '((3 4) (4 3)) ; order doesn't matter 
    > (my-permutations '(2 3 4))
    '((2 3 4) (3 2 4) (3 4 2) (2 4 3) (4 2 3) (4 3 2))
    > (my-permutations '(1 2 3 4))
    '((1 2 3 4) (2 1 3 4) (2 3 1 4) (2 3 4 1) 
      (1 3 2 4) (3 1 2 4) (3 2 1 4) (3 2 4 1) 
      (1 3 4 2) (3 1 4 2) (3 4 1 2) (3 4 2 1)
      (1 2 4 3) (2 1 4 3) (2 4 1 3) (2 4 3 1) 
      (1 4 2 3) (4 1 2 3) (4 2 1 3) (4 2 3 1) 
      (1 4 3 2) (4 1 3 2) (4 3 1 2) (4 3 2 1))

    Note: It is helpful to use append-all, map, and inserts-foldr in your solution. If you are unable to define inserts-foldr from the previous subproblem, you may use the following recursive version of the inserts function from PS2 Problem 4:

    (define (inserts-rec x ys)
      (if (null? ys)
          (list (list x))
          (cons (cons x ys)
                (map-cons (first ys)
                          (inserts-rec x (rest ys))))))

4. forall?, exists?, find, and zip (20 points)

Below are some list-processing functions that are not built in to Racket, but are handy in many situations:

(define (forall? pred xs)
  (or (null? xs)
      (and (pred (first xs))
           (forall? pred (rest xs)))))

(define (exists? pred xs)
  (and (not (null? xs))
       (or (pred (first xs))
           (exists? pred (rest xs)))))

(define (find pred not-found xs)
  (if (null? xs)
      not-found
      (if (pred (first xs))
          (first xs)
          (find pred not-found (rest xs)))))

(define (zip xs ys)
  (if (or (null? xs) (null? ys))
      '()
      (cons (cons (first xs) (first ys))
            (zip (rest xs) (rest ys)))))

;; Can also define zip using Racket's mutlilist map
;; (but need to make both list args have same length;
;; otherwise Racket map will generate error if theyr don't) 
;;
;; (define (zip xs ys)
;;   (let ((minlen (min (length xs) (length ys))))
;;     (map cons (take xs minlen) (take ys minlen))))

forall?, exists?, and find are higher-order list functions involving a predicate.

  • forall? returns #t if the predicate is true on all elements of the list, and otherwise returns #f.

      > (forall? (λ (x) (> x 0)) '(7 2 5 4 6))
      #t
      > (forall? (λ (x) (> x 0)) '(7 2 -5 4 6))
      #f
  • exists? returns #t if the predicate is true on at least one element of the list, and otherwise returns #f.

      > (exists? (λ (x) (< x 0)) '(7 2 -5 4 6))
      #t
      > (exists? (λ (x) (< x 0)) '(7 2 5 4 6))
      #f
  • find returns the first element of the list for which the predicate is true. If there is no such element, it returns the value supplied as the not-found argument.

      > (find (λ (x) (< x 0)) #f '(7 2 -5 4 -6))
      -5
      > (find (λ (x) (< x 0)) #f '(7 2 5 4 6))
      #f

The zip function is not higher order, but combines two lists by pairing (using cons) the corresponding elements of the two lists. If the lists do not have the same length, zip returns a list of pairs whose length is the length of the shorter of the two input lists:

    > (zip '(1 2 3) '("a" "b" "c"))
    '((1 . "a") (2 . "b") (3 . "c"))
    > (zip '(1 2 3 4 5) '("a" "b" "c"))
    '((1 . "a") (2 . "b") (3 . "c"))
    > (zip '(1 2 3) '("a" "b" "c" "d" "e"))
    '((1 . "a") (2 . "b") (3 . "c"))

In this problem, you will use the forall?, exists?, find, and zip functions to define other functions. Begin this problem by copying the definitions of these four functions into the top of your yourAccountName-ps4-functions.rkt file.

  1. (3 points) Using exists?, define a function member? that determines if an element x appears in a list ys.

     > (member? 4 '(7 2 5 4 6))
     #t
     > (member? 3 '(7 2 5 4 6))
     #f
     > (member? '(7 8) '((1 2) (3 4 5) (6) (7 8) () (9)))
     #t
     > (member? '() '((1 2) (3 4 5) (6) (7 8) () (9)))
     #t
     > (member? '(5 6) '((1 2) (3 4 5) (6) (7 8) () (9)))
     #f

    Note: Use equal? to compare the equality of two values.

  2. (5 points) Using forall? and exists?, define a function all-contain-multiple-alt? that is an alternative implementation of the all-contain-multiple? function from Problem 3.

     > (all-contain-multiple-alt? 5 '((17 10 2) (25) (3 8 5)))
     #t
     > (all-contain-multiple-alt? 2 '((17 10 2) (25) (3 8 5)))
     #f
     > (all-contain-multiple-alt? 3 '())
     #t ; said to be "vacuously true"; there is no counterexample!

    Note: You may use the divisible_by function from above, but not the contains-multiple? function, and you may not define any new helper functions.

  3. (4 points) An association list is a list of pairs that represents a mapping from key to value. Each pair of key and value is represented by a cons cell, with the key in the car and the value in the cdr. For example, the association list:

     '((2 . 3) (5 . 1) ("mountain" . #t))

    maps the key 2 to the value 3, the key 5 to the value 1, and the key "mountain" to the value #t.

    Using find, define a function lookup that takes a key k and an association list as and returns:

    • #f if no mapping with key k was not found in the list; and
    • a cons cell whose car is k and whose cdr is the corresponding value for the shallowest mapping of k in the association list.

    For example:

     > (lookup 5 '((2 . 3) (5 . 1) ("mountain" . #t)))
     '(5 . 1)
     > (lookup 1 '((2 . 3) (5 . 1) ("mountain" . #t)))
     #f
     > (lookup '(6 4) '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42)))
     '((6 4) . 8)
     > (lookup 17 '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42)))
     '(17 23 42) ; '(17 23 42) has a car of 17 and a cdr of '(23 42)
     > (lookup 23 '((2 . 3) (5 . 1) ((6 4) . 8) (5 . 1) (17 23 42)))
     #f

    Note: Use equal? to test for equality of keys. This will support keys more interesting than just simple values.

  4. (5 points) Using forall? and zip, define a function sorted? that determines if a list of numbers ns is in sorted order from low to high.

     > (sorted? '(7 4 2 5 4 6))
     #f
     > (sorted? '(2 3 3 5 6 7))
     #t
     > (sorted? '(2))
     #t
     > (sorted? '())
     #t
     > (sorted? (range 1000))
     #t
     > (sorted? (append (range 1000) '(1001 1000)))
     #f
     > (sorted? (range 1000 0 -1))
     #f

    Note: You will need to have a special case for the empty list.

  5. (3 points) It is possible to define alternative versions of forall? and exists? in terms of foldr, as show below.

     (define (forall-alt? pred xs)
       (foldr (λ (x subres) (and (pred x) subres))
              #t
              xs))
    
     (define (exists-alt? pred xs)
       (foldr (λ (x subres) (or (pred x) subres))
              #f
              xs))
    
     > (forall-alt? (λ (x) (> x 0)) '(7 2 5 4 6))
     #t
     > (forall-alt? (λ (x) (> x 0)) '(7 2 -5 4 6))
     #f
     > (exists-alt? (λ (x) (< x 0)) '(7 2 -5 4 6))
     #t
     > (exists-alt? (λ (x) (< x 0)) '(7 2 5 4 6))

    However, just because it’s possible to define a function in terms of foldr does not mean its a good idea. Give a concrete example of a situation in which forall? is better than forall-alt?.

    Note: For this problem, it’s critical to understand that (and e1 e2) desugars to (if e1 e2 #f)

5. foldr-ternop (10 points)

Sometimes it is difficult to express a recursive list accumulation in terms of foldr because the binary combiner function needs more information from the list than its first element. The following foldr-ternop higher-order list function solves this problem by having the combiner function be a ternary (i.e., three-argument) function that takes both the first and rest of the given list in addition to the result of recursively processing the list:

(define (foldr-ternop ternop null-value xs)
  (if (null? xs)
      null-value
      (ternop (first xs)
              (rest xs)
              (foldr-ternop ternop null-value (rest xs)))))

In this problem, you will use foldr-ternop to implement two list functions that are challenging to implement in terms of foldr (you already did inserts-foldr above; see the extra credit problem for sorted?-foldr below). Begin this problem by copying the definition of foldr-terntop into the top of your yourAccountName-ps4-functions.rkt file.

  1. (5 points) Using foldr-ternop, define a function inserts-foldr-ternop that takes a value x and an n-element list ys and returns an n+1-element list of lists showing all ways to insert a single copy of x into ys.

     > (inserts-foldr-ternop 3 '(5 7 1))
     '((3 5 7 1) (5 3 7 1) (5 7 3 1) (5 7 1 3))
     > (inserts-foldr-ternop 3 '(7 1))
     '((3 7 1) (7 3 1) ( 7 1 3))
     > (inserts-foldr-ternop 3 '( 1))
     '((3 1) (1 3))
     > (inserts-foldr-ternop 3 '())
     '((3))
     > (inserts-foldr-ternop 3 '(5 3 1))
     '((3 5 3 1) (5 3 3 1) (5 3 3 1) (5 3 1 3))

    Notes:

    • Your definition should have exactly this pattern:

      (define (inserts-foldr-ternop x ys)
        (foldr-ternop {ternary-combiner} {null-value} ys))
    • You may use map-cons in your ternary-combiner function.

  2. (5 points) Using foldr-ternop, define a function sorted-alt? that is an alternative implementation of the sorted? function from Problem 4.

     > (sorted-alt? '(7 4 2 5 4 6))
     #f
     > (sorted-alt? '(2 3 3 5 6 7))
     #t
     > (sorted-alt? '(2))
     #t
     > (sorted-alt? '())
     #t
     > (sorted-alt? (range 1000))
     #t
     > (sorted-alt? (append (range 1000) '(1001 1000)))
     #f
     > (sorted-alt? (range 1000 0 -1))
     #f

    Note:

    • Your definition should have exactly this pattern:

      (define (sorted-alt? xs)
         (foldr-ternop ; ternary-combiner goes here
                       ; null-value goes here
                       xs))

Extra Credit1: Using foldr to define sorted? (8 points)

This problem is optional. You should only attempt it after completing all the other problems.

As noted in Problem 5, it is challenging to define sorted in terms of foldr, but it turns out that it is stil possible to do this.

Using foldr, define a function sorted?-foldr that is an alternative implementation of the sorted? function from Problem 5.

    > (sorted?-foldr (list 7 4 2 5 4 6))
    #f
    > (sorted?-foldr (list 2 3 3 5 6 7))
    #t
    > (sorted?-foldr (list 2))
    #t
    > (sorted?-foldr (list))
    #t

Note:

  • Your definition should have exactly the following pattern:

    (define (sorted?-foldr ys)
      (cdr (foldr ; binary combiner goes here
                  (cons ; null-value1
                        ; null-value2
                        )
                  ys)))

    The idea is to accumulate a pair of (1) the first element of the rest of the list (or #f if there is none) and (2) a boolean indicating whether the rest of the list is sorted.

Extra Credit 2: Recursion Via the Y Combinator (15 points)

This problem is optional. You should only attempt it after completing all the other problems.

Consider the following sumTo function in Racket, which sums the numbers from 1 up to its argument.

(define sumTo (λ (n)
                (if (<= n 0)
                    0
                    (+ n (sumTo (- n 1))))))

> (sumTo 3)
6 

> (sumTo 10)
55

> (sumTo 100)
5050 ; 50 pairs of numbers like (+ 1 100), (+ 2 99), (+ 3 98), etc,, where each pair sums to 101.

We will use λ_sumTo as an abbreviation for the lambda expression bound to the name sumTo.

Small-step semantics explains how such recursions work. In all the recursion examples we’ve seen, define is used to name the recursive function in an environment, and then this name is looked up in the environment when the recursive function is called. This allows the function to refer to itself, and is an important detail for ``tying the knot of recursion”.

It is a remarkable fact that recursion in Racket (and any other languages with first-class anoymouse functions) does not require environments (or even letrec), and can be done purely via lambda and the substitution model. This is shown by example in this problem.

Here is a very special Racket lambda expression known as the Y combinator, Y operator, or fixed-point combinator.

(λ (funToFun)
    ((λ (z) (z z))
     (λ (x) (funToFun (λ (v) ((x x) v)))))))

Let’s call this function λ_fix. It takes as its argument a function funToFun that maps an input function to an output function. For example, funToFun might be the following function, which we’ll call λ_sumToGen:

(λ (f)
  (λ (n)
    (if (<= n 0)
        0
        (+ n (f (- n 1))))))

Note when λ_sumToGen is applied to some input function, the output function is almost always different from the input function. For example:

  • If λ_sumToGen it is applied to the function (λ (m) (+ m 1)), it returns a function equivalent to (λ (n) (if (<= n 0) 0 (+ n n))), which behaves very differently from (λ (m) (+ m 1)).

  • If λ_sumToGen it is applied to the function (λ (m) (* m 2)), it returns a function equivalent to (λ (n) (if (<= n 0) 0 (+ n (* 2 (- n 1))))), which is equivalent to (λ (n) (if (<= n 0) 0 (- (* 3 n) 2))). Again, this output function is very different from the input function (λ (m) (* m 2)).

  • If λ_sumToGen it is applied to a factorial function (call it λ_fact), it returns a function equivalent to (λ (n) (if (<= n 0) 0 (+ n (λ_fact (- n 1))))). Because this adds n to the factorial of (n-1) rather than multiplying n by the factorial of (n-1), this output function is not the same as the input factorial function.

However, there is one exception: If λ_sumToGen it is applied to the sumTo function (i.e. λ_sumTo), it returns a function equivalent to (λ (n) (if (<= n 0) 0 (+ n (λ_sumTo (- n 1))))). In this case, the output function is just another version of the sumTo function! Because the input and output functions are the same in this case, we say that λ_sumTo is the fixed point of λ_sumToGen — i.e., it is a value Vsuch that (λ_sumToGen V) is the same as V.

Remarkably, when the fixed-point combinator λ_fix is applied to λ_sumToGen it returns a function equivalent to λ_sumTo — that is, it returns the fixed point of λ_sumToGen. This is why λ_fix is called the fixed-point combinator.

Your task is to show that (λ_fix λ_sumToGen) behaves like λ_sumTo by showing the following derivations in small-step semantics:

  • ((λ_fix λ_sumToGen) 0) ⇒* 0 (i.e., the sum of nonnegative integers up to and including 0)
  • ((λ_fix λ_sumToGen) 1) ⇒* 1 (i.e., the sum of nonnegative integers up to and including 1)
  • ((λ_fix λ_sumToGen) 2) ⇒* 3 (i.e., the sum of nonnegative integers up to and including 2)
  • ((λ_fix λ_sumToGen) 3) ⇒* 6 (i.e., the sum of nonnegative integers up to and including 3)

You can abbreviate certain sequences of ⇒ steps via ⇒* as long as you clearly show the essence of how these reductions work.

This is tedious, but it is amazing to see how the λ_fix is able to perform recursion without using define to name the recursive function in the global environment, or without using letrec to name a local recursive function.

You can also verify that this works in Racket. For example, the sum of nonnegative integers up to and including 100 is 5050:

> (((λ (funToFun) ; this is λ_fix 
      ((λ (z) (z z))
       (λ (x) (funToFun (λ (v) ((x x) v))))))
    (λ (f) ; this is λ_sumToGen
      (λ (n)
        (if (<= n 0)
            0
            (+ n (f (- n 1)))))))
    100) ; this whole expression is ((λ_fix λ_sumToGen) 100)
5050