CS 217: Programming Languages

Programming Assignment 1: Scheme A

Assigned on Wednesday, 1/2.
Due by 11:55 PM on Wednesday, 1/19.


Submission instructions: If you are working in a team, one member of the team should use hsp to turn in an entire directory called scheme_team_a that contains the files warmup.scm, pairsum.scm, bst.scm, and numbers2words.scm. Make sure that both team members are credited in program comments. For the individual assignment at the bottom of this web page, each student should individually use hsp to submit a directory called scheme_indiv_a that contains the file sets.scm.

Note that these problems vary significantly in amount of difficulty. You should start early and look at each problem.

You should use recursion, and not iteration, on each of these problems.

For all parts of this assignment, you may not use side-effects.


Team problems

Warmup

Write a function named sum that adds up all the elements in a list. For example:

(sum '(4 5 0 1)) ---> 10

Binary Search Trees

Write Scheme functions that implement integer binary search trees. A non-empty binary search tree can be represented as a list of three entries. The first entry is a node's value (an integer), the middle is the node's left subtree, and the last is a node's right subtree. Non-empty left and right subtrees are themselves lists of three elements. An empty tree is represented as an empty list. For example, the list

(5 () ())

represents a tree whose only node is the leaf 5. The list

(5 (3 () (4 () ()) ) ())

represents the tree whose root node is 5 and has only one child, a left child, 3: this left child has only one child, a right child, 4. In a binary search tree it is always the case that values in the left subtree are less than the root's value, and values in the right subtree are greater than the root's value. You may assume that a binary search tree that is given to you will never have duplicate entries, but you will need to make sure when inserting a new entry that it is not a duplicate.

For all parts of this question, you may not use side-effects (e.g. set!).

Write the following functions:

(null-bst)
Return an empty tree.

(null-bst? bst)
Return true (#t) if bst is empty, and false (#f) otherwise.

(entry bst)
(left bst)
(right bst)

These functions return the node, left subtree, and right subtree, respectively, of bst. If bst is empty or if bst does not contain three entries where the last two are lists, return #f.

(make-bst elt left right)
This function creates a new tree whose root node is elt, left subtree is left, and right subtree is right. You should check that elt is in fact a number, and that left and right are either empty lists or lists of three elements each where the last two are lists. Return #f is the input is bad.

(preorder bst)
Print each node in bst in preorder (the root of bst, then the left subtree in preorder, then the right subtree in preorder). Use the Scheme function (print) to write out each number, and use the Scheme code (write-char #\space) to put a space between each item that you print.

(inorder bst)
Print each node in bst inorder (the left subtree inorder, then the node, then the right subtree inorder).

(postorder bst)
Print each node in bst in postorder (the left subtree in postorder, then the right subtree in postorder, then the node).

(insert v bst)
Create a new binary search tree identical to bst but with integer v inserted in its proper location. Smaller values are inserted to the left of a node, larger values to the right of a node. If v is already in the tree, just return the original tree without changing it. You may assume that bst and all its subtrees are valid binary search trees.

Lazy Lists

(a) Write a pair of Scheme functions, (gen-list start stop) and (pair-sum? list val). The function gen-list will generate a list of consecutive integers, from start to stop. (If start > stop then the empty list is generated.) For example:

(gen-list 1 5) ---> (1 2 3 4 5)

The predicate pair-sum? tests whether any two adjacent values in numlist sum to val. For example,

(pair-sum? '(1 2 3) 3) ---> #t

since 1+2=3. Similarly,

(pair-sum? (gen-list 1 100) 1000) ---> #f

since no two adjacent integers in the range 1 to 100 can sum to 1000.

(b) An alternative to generating the entire list of numbers is to instead produce a lazy list. Consider the following example.

(define (gen-lazy-list start stop)
  (if (> start stop)
      #f
      (cons start
          (lambda () (gen-lazy-list (+ start 1) stop)))))

When called, gen-lazy-list defines a pair of values. The car is the first integer in the sequence. The cdr is a function that, when called, will return another pair. That pair consists of the next integer in the sequence plus another suspension function. When the end of the sequence is reached, the function in the cdr of the pair returns #f, indicating that no more values can be produced.

Rewrite your solution to pair-sum? as a new function called pair-sum-lazy? that takes a lazy integer sequence as defined by gen-lazy-list.

Converting Numbers to Words

Scheme can represent numbers in a variety of forms, including integer, rational, real, and complex. Let's add a new form - English. That is, we will allow numbers to be represented as a list of symbols representing English language words corresponding to numbers. Thus 0 would be represented as (zero), 123 as (one hundred twenty three) and -456789 as (minus four hundred fifty six thousand seven hundred eighty nine).

Assume that we represent numbers (in English) using the symbols minus, zero, one, two, ..., ten, eleven, twelve, ..., nineteen, twenty, thirty, ..., ninety, hundred, thousand, million, ... For simplicity, numbers that are normally hyphenated (like twenty-three) won't be.

(a) Write a Scheme function (int->eng int-val) that converts an integer int-val into English form (that is, a list of symbols representing the number in English). Your function should handle numbers at least as large as one trillion (handling even larger numbers is fairly easy). For numbers larger than your implementation limit (of one trillion or more) you may return the integer unconverted. You may find the Scheme functions quotient, remainder, expt, and/or abs useful.

(b) Now write an inverse function (eng->int L) that converts a number in English form back to integer form. Of course it should be the case that

(eng->int (int->eng int-val)) = int-val

for all integers in the range you handle. You may assumed that this number in English is well-formed.

(c) Finally, redefine the +, -, *, and quotient functions so that they accept numbers in English form as well as integer form (you may ignore numbers in rational, real, and complex forms). You may assume that your extended operators will be called with only two operands. If both operands are in integer form, then you will return in an integer result. If one or both are in English form, then you will return a result in English form. For /, you may use truncate to convert fractional quotients to integers; that is, you need not worry about divisions that have a non-zero remainder.

The following are sample calls that you should handle:

(+ 123 456) ---> 579

(- 111111 '(ten)) ---> (one hundred eleven thousand one hundred one)

(* '(two thousand fifty five) '(one million one))
  ---> (two billion fifty five million two thousand fifty five)

(quotient 123456789 '(three)) --->
  (forty one million one hundred fifty two thousand
   two hundred sixty three) 

Note that redefining the built in arithmetic operations is in general dangerous (but fun for our purposes!). Once you start playing with this, be aware that you may need to quit DrScheme and restart it each time that you run your code.


Individual Problem

Set Operations

In Scheme sets can be represented as lists. However, unlike lists, the order of values in a set is not significant. Thus both (1 2 3) and (3 2 1) represent the same set.

(a) Write a Scheme function (set-equal? S1 S2) that tests whether S1 and S2 (represented as lists) are equal. Two sets are equal if they contain exactly the same members, ignoring ordering. In this part you may assume that sets contain only atomic values (numbers, string, symbols, etc.) For example

(set-equal? '(1 2 3) '(3 2 1)) ---> #t
(set-equal? '(1 2) '(3 2 1)) ---> #f
(set-equal? '(susan john lyta) '(lyta john susan)) ---> #t 

You may assume that no sets contain duplicate members.

(b) Two common operations on sets are union and intersection. The union of two sets is the set of all elements that appear in either set (with no repetitions). The intersection of two sets is the set of elements that appear in both sets.

Write Scheme functions (union S1 S2) and (intersect S1 S2) that implement set union and set intersection. You may again assume that set elements are always atomic, and that there are no duplicates. For example

(union '(1 2 3) '(3 2 1)) ---> (1 2 3)
(union '(1 2 3) '(3 4 5)) ---> (1 2 3 4 5)
(union '(a b c) '(3 2 1)) ---> (a b c 1 2 3)
(intersect '(1 2 3) '(3 2 1)) ---> (1 2 3)
(intersect '(1 2 3) '(4 5 6)) ---> ()
(intersect '(1 2 3) '(2 3 4 5 6)) ---> (2 3) 

The ordering of the elements in your answer may differ from the above.


Many thanks to Prof. Charles Fischer at University of Wisconsin-Madison.