CS 334: Lab 2: Lisp Higher-Order Functions

Overview

In this lab, you will explore higher-order functions in Lisp by implementing filter and using it to build several useful abstractions including set operations and quantifiers.

You may work with a partner on this lab if you like, but it is not required. If you'd like to be matched with a partner, let the instructor know.

Setting Up Your Repository

You will receive an email with an invitation link to the lab2 assignment on GitHub Classroom. Here's what to expect:

  1. Click the invite link in the email. This takes you to GitHub Classroom. If prompted, authorize GitHub Classroom to access your GitHub account.

  2. Accept the assignment by clicking the green "Accept this assignment" button.

  3. Wait for your repository to be created. GitHub Classroom will copy the starter code into a new private repository for you. This usually takes a few seconds.

  4. Look for the link to your new repository on the confirmation page. You may need to refresh the page if it says the repository is still being created or no longer exists.
    You can also check your email for a notification from GitHub with a link to your new repo.

Setting Up SSH Keys for GitHub

Some of you had authentication issues with GitHub last week. The instructions below will help you set up you account on our Unix machines so that you can clone, push, and pull using an ssh key for authentication. If you have already set up a key, you can just skip ahead to the lab below. These steps save the ssh key in your home directly, so you should only need to do this once.

  1. Generate an SSH key pair. First, check if you already have a key by running

    cat ~/.ssh/id_rsa.pub
    

    If that prints a key, skip to step 2. Otherwise, run:

    ssh-keygen -t rsa
    

    When prompted for a file location, press Enter to accept the default. You can also press Enter to skip setting a passphrase (or set one if you prefer).

  2. Copy your public key. Display it with:

    cat ~/.ssh/id_rsa.pub
    

    Select and copy the entire output.

  3. Add the key to GitHub. Go to github.com/settings/keys, click New SSH key, give it a title (e.g., "CS lab machine"), paste the key into the Key field, and click Add SSH key.

  4. Test the connection:

    ssh -T git@github.com
    

    You should see a message like Hi username! You've successfully authenticated....

  5. Clone using the SSH URL. When cloning your repository, use the SSH URL (starts with git@github.com:) rather than the HTTPS URL:

    git clone git@github.com:williams-cs/cs334-lab2-YOURNAME.git
    

Programming Problems

1. Filter

We've already seen how using mapcar provides a generic way to easily manipulate collections of data. There are others that are equally useful. We examine one of them in this lab.

a. Write a function filter that takes a predicate function p and a list l. This function returns a list of those elements in l that satisfy the criteria specified by p. For example, the following two examples filter all negative numbers out of a list and filter all odd numbers out of a list:

* (filter #'(lambda (x) (>= x 0)) '(-1 1 2 -3 4 -5))
(1 2 4)

* (defun even (x) (eq (mod x 2) 0))
* (filter #'even '(6 4 3 5 2))
(6 4 2)

You will need to use the built-in operation funcall to call the function passed to filter as a parameter. That is, the function

(defun example (f)
    (funcall f a1 ... an)
)

applies f to arguments a1 -- an. You may not use the built-in functions remove-if and remove-if-not in your solution.

b. Suppose that we are using lists to represent sets (in which there are no repeated elements). Use your filter function to define functions set-union and set-interset that take the union and intersection of two sets, respectively:

* (set-union '(1 2 3) '(2 3 4))
(1 2 3 4)
* (set-intersect '(1 2 3) '(2 3 4))
(2 3)

You may find the built-in function (member x l) described in the 334 Lisp FAQ handy.

c. Now, use filter to implement the function exists. Given a predicate function p and a list l, this function returns true if there is at least one a in l such that (p a) returns true:

* (exists #'(lambda (x) (eq x 0)) '(-1 0 1))
t
* (exists #'(lambda (x) (eq x 2)) '(-1 0 1))
nil

You may assume that p will terminate without crashing for all a.

The function all returns true if (p a) is true for all a in l:

* (all #'(lambda (x) (> x -2)) '(-1 0 1))
t
* (all #'(lambda (x) (> x 0)) '(-1 0 1))
nil

The function count-elems returns the number of elements in l for which (p a) is true:

* (count-elems #'(lambda (x) (> x 0)) '(-1 0 1 2 3))
3
* (count-elems #'even '(1 2 3 4 5 6))
3

You should not need to recursively traverse directly for any of these.

Make sure your code uses count-elems.

If you were in the 11am lab, you may have starter code that uses count instead of count-elems. This will cause an error since clisp doesn't let you redefine that symbol.

d. In a comment in your code, explain in 1-2 sentences why using filter to implement exists and all may not be the most efficient approach.

2. Apply N-Times

Implement apply-n-times, which applies a function f to a value x repeatedly n times:

* (apply-n-times #'cdr 2 '(a b c d e))
(c d e)

* (apply-n-times #'(lambda (x) (* x 2)) 5 1)
32

* (apply-n-times #'(lambda (x) (cons 'a x)) 3 nil)
(a a a)

Then use apply-n-times to implement nth-elem, which returns the nth element of a list (0-indexed):

* (nth-elem 0 '(a b c d))
a
* (nth-elem 2 '(a b c d))
c

Make sure your code uses nth-elem.

Be sure your code uses nth-elem instead of nth, for the same reason as above.

3. Python Crossover

Consider this Python code that counts how many words in a list have more than 4 letters:

def count_long_words(words):
    count = 0
    for word in words:
        if len(word) > 4:
            count += 1
    return count

a. Rewrite this in Python using a list comprehension instead of a loop. Your solution should be one line.

b. Which version do you find more readable? (Note: This is asking you to compare the two Python versions: the loop version and the comprehension version. You can ignore the 3-python-crossover.lisp file if you see it in your repo -- that was inadvertently included and I removed it, possibly after you cloned the starter.) Write 1-2 sentences comparing the approaches. Write your answer in a comment in your python file.

Submitting Your Work

Submit your code to the GradeScope assignment named, for example, "Lab 1". You can submit in one of two ways:

  • Upload files: Click "Upload" and select all of your source files, or
  • Link GitHub: Click "GitHub" and select your repository and branch.

Please do not change the names of the starter files. Also:

  • If you worked with a partner, only one of each pair needs to submit the code.
  • Indicate who your partner is when you submit. Specifically, after you upload your files, there will be an "Add Group Member" button on the right of the Gradescope webpage -- click that and add your partner.

Autograding: Gradescope will run an autograder on your code that performs some simple tests. Be sure to look at the autograder output to verify your code works as expected. We will run more extensive tests on your code after the deadline.