#| 0. Write your name and OU ID (the part before the "@" in your email address) below: NAME: ID: Many of the exercises in this file have something to do with Pyret lists. I recommend -- before you get started -- that you peruse Pyret's list library documentation, available here: https://www.pyret.org/docs/latest/lists.html. |# #| PART I (Lists): 1. As a warmup, define a function, my-len, that takes a List as argument and returns the list's length as its result. Your function should pass all the test cases given below. NOTE 1: Try to define my-len yourself, without going back to PAPL 6. NOTE 2: Don't use any library functions (like "len") in this exercise. Instead, uses "cases" and recursion to calculate list length yourself. |# fun my-len(l :: List) -> Number: ... end check: my-len([list: 0]) is 1 my-len([list: ]) is 0 my-len([list: 1, 2, 3, 4, 5]) is 5 my-len([list: 1, 2]) is 2 my-len([list: false]) is 1 my-len([list: [list: 1]]) is 1 end #| 2. As warmup #2, define a function, my-append, that takes two List arguments l1 and l2, for any type T, and returns the concatenation of the two lists. That is, your function should return a single lists that contains, first, all the elements of l1, then all the elements of the second list l2. |# fun my-append(l1 :: List, l2 :: List) -> List: ... end check: my-append([list: ], [list: ]) is [list: ] my-append([list: ], [list: 1]) is [list: 1] my-append([list: 1], [list: ]) is [list: 1] my-append([list: 1, 2], [list: 3]) is [list: 1, 2, 3] my-append([list: [list: ]], [list: [list: ]]) is [list: [list:], [list:]] end #| 3. Using just case analysis and recursion, define a function, called knil, that links a value of type T onto the *end* of a List. For example: knil(1, [list: 3, 2]) should result in [list: 3, 2, 1]. HINT: Structure your program, as you presumably did previously in 1 and 2, as a case analysis on the input list l. Think recursively in the case when l is a link(f, r), with head or front element f and tail or rest r. What's a recursive way in which to move the new element "a" toward the end of the list, while maintaining the correct structure of the list up to that point? |# fun knil(a :: T, l :: List) -> List: ... end check: knil(1, [list: 3, 2]) is [list: 3, 2, 1] knil(0, [list: ]) is [list: 0] knil(false, [list: true, false]) is [list: true, false, false] end #| 4. Using your implementation of knil in 3, define a function, my-rev, that reverses a list. For example, my-rev([list: 1, 2, 3]) should result in [list: 3, 2, 1]. |# fun my-rev(l :: List) -> List: ... end check: my-rev([list: 1, 2, 3]) is [list: 3, 2, 1] my-rev([list: ]) is [list: ] my-rev([list: 1]) is [list: 1] my-rev([list: false, true]) is [list: true, false] end #| 5. Using your my-append function from 2, define a function, my-flatten, that takes a list of lists and flattens it into a single list. For example, my-flatten([list: [list: 1], [list: ], [list: 2, 3]]) should result in the list [list: 1, 2, 3]. |# fun my-flatten(l :: List>) -> List: ... end check: my-flatten([list: [list: 1], [list: ], [list: 2, 3]]) is [list: 1, 2, 3] my-flatten([list: [list: ]]) is [list: ] my-flatten([list: [list: [list: ]]]) is [list: [list: ]] my-flatten([list: [list: 1, 2, 3]]) is [list: 1, 2, 3] my-flatten([list: [list: false]]) is [list: false] my-flatten([list: [list: false], [list: ]]) is [list: false] end #| 6. Define a function, my-twice, that takes an arbitrary function f :: (T -> T) and an initial value a :: T and returns the result of applying f to a twice. |# fun my-twice(f :: (T -> T), a :: T) -> T: ... end check: my-twice(lam(x): x + 2 end, 3) is 7 my-twice(lam(x): x end, false) is false my-twice(lam(x): my-twice(lam(y): y + 1 end, x) end, 0) is 4 end #| 7. Define a function, my-maptwice, that takes an arbitrary function f :: (T -> T) and a list of values of type T and returns the result of applying f twice to each value in the list. |# fun my-maptwice(f :: (T -> T), l :: List) -> List: ... end check: my-maptwice(lam(x): x + 2 end, [list: 1, 2, 3]) is [list: 5, 6, 7] my-maptwice(lam(x): x - 1 end, [list: ]) is [list: ] my-maptwice(lam(x): x - 1 end, [list: 0]) is [list: -2] my-maptwice(lam(x): my-twice(lam(y): y - 1 end, x) end, [list: 1, 2, 3, 4, 5]) is [list: -3, -2, -1, 0, 1] end #| 8: Define a function, isort, that uses the insertion sort algorithm to sort a list of numbers, of type List. |# fun isort(l :: List) -> List: ... end check: isort([list: 3, 2, 1]) is [list: 1, 2, 3] isort([list:]) is [list:] isort([list: 1]) is [list: 1] isort([list: 3, 5, -7, 1, 0, 200]) is [list: -7, 0, 1, 3, 5, 200] end #| PART II (Trees): In the next few exercises, you'll explore the following datatype, BTree, which defines polymorphic binary search trees parameterized by the type T of data contained at the leaves. |# data BTree: | leaf(val :: T) | node(left :: BTree, right :: BTree) end #| 1. WARMUP: Referring to the definition of binary trees BTree above, define a function, num-leaves, that returns the number of leaves (NOTE: NOT the total number of nodes!) in a given BTree. |# fun num-leaves(b :: BTree) -> Number: ... where: num-leaves(leaf(3)) is 1 num-leaves(node(leaf(3), leaf(4))) is 2 num-leaves(node(node(leaf(3), leaf(4)), leaf(5))) is 3 num-leaves(node(node(leaf(3), leaf(4)), node(leaf(3), leaf(4)))) is 4 end #| 2. WARMUP: Define a function, num-nodes, that returns the TOTAL number of nodes in a tree. |# fun num-nodes(b :: BTree) -> Number: ... where: num-nodes(leaf(3)) is 1 num-nodes(node(leaf(3), leaf(4))) is 3 num-nodes(node(node(leaf(3), leaf(4)), leaf(5))) is 5 num-nodes(node(node(leaf(3), leaf(4)), node(leaf(3), leaf(4)))) is 7 end #| 3. WARMUP: Define a function, contains, that returns true if a BTree contains a particular number n. |# fun contains(b :: BTree, n :: Number) -> Boolean: ... where: contains(leaf(3), 2) is false contains(leaf(3), 3) is true contains(node(leaf(3), leaf(4)), 5) is false contains(node(leaf(3), leaf(4)), 4) is true contains(node(leaf(3), leaf(4)), 3) is true contains(node(node(leaf(3), leaf(4)), node(leaf(3), leaf(5))), 5) is true contains(node(node(leaf(3), leaf(4)), node(leaf(3), leaf(5))), 2) is false end #| A binary /search/ tree, in contrast to a regular old binary tree, is one in which: (i) The nodes -- not the leaves -- contain the data, and (ii) The data items are ordered such that for any node, the data items in the node's left child are less than the data item in the node and the data items in the node's right child are greater than the data item in the node. If you've forgotten how binary search trees work, you can refresh your memory here: https://en.wikipedia.org/wiki/Binary_search_tree. |# data BSTree: | bst-leaf | bst-node(val :: Number, left :: BSTree, right :: BSTree) end #| The binary search tree invariant, as described in the comment above the declaration of BSTree, can be encoded as a function in Pyret as follows: |# fun all-satisfy(b :: BSTree, f :: (Number -> Boolean)): cases(BSTree) b: | bst-leaf => true | bst-node(m, l, r) => f(m) and all-satisfy(l, f) and all-satisfy(r, f) end end fun bst-inv(b :: BSTree) -> Boolean: cases(BSTree) b: | bst-leaf => true | bst-node(m, l, r) => bst-inv(l) and bst-inv(r) and all-satisfy(l, lam(n): n < m end) and all-satisfy(r, lam(n): n > m end) end end # Trees used for testing below b1 = bst-node(3, bst-leaf, bst-leaf) b2 = bst-node(4, bst-node(1, bst-leaf, bst-leaf), bst-node(5, bst-leaf, bst-leaf)) b3 = bst-node(7, bst-node(4, bst-node(3, bst-leaf, bst-leaf), bst-node(5, bst-leaf, bst-leaf)), bst-node(9, bst-node(8, bst-leaf, bst-leaf), bst-node(10, bst-leaf, bst-leaf))) b4 = bst-node(3, bst-node(3, bst-leaf, bst-leaf), bst-leaf) b5 = bst-node(7, bst-node(4, bst-node(4, bst-leaf, bst-leaf), bst-node(5, bst-leaf, bst-leaf)), bst-node(9, bst-node(8, bst-leaf, bst-leaf), bst-node(10, bst-leaf, bst-leaf))) # END Trees used for testing check: bst-leaf satisfies bst-inv b1 satisfies bst-inv b2 satisfies bst-inv b3 satisfies bst-inv b4 violates bst-inv b5 violates bst-inv end #| 4. Define a function, bst-lookup, that determines whether a given number is present in a binary search tree. You may assume that the BSTree passed as an argument to bst-lookup satisfies the binary search tree invariant as expressed in function bst-inv. |# fun bst-lookup(b :: BSTree, n :: Number) -> Boolean: ... where: bst-lookup(bst-leaf, 3) is false bst-lookup(b1, 2) is false bst-lookup(b1, 3) is true bst-lookup(b2, 1) is true bst-lookup(b2, 4) is true bst-lookup(b2, 6) is false bst-lookup(b3, 5) is true bst-lookup(b3, 8) is true bst-lookup(b3, 10) is true bst-lookup(b3, 11) is false end #| 5. Define a function, bst-insert, that inserts a number into a binary search tree such that the following property holds: if bst-inv(b) is true initially, then bst-inv(bst-insert(b, n)) is true after the insertion. NOTE: If the number is already in the tree, then your bst-insert function should leave the tree unchanged. |# fun bst-insert(b :: BSTree, n :: Number) -> BSTree: ... end check: bst-insert(bst-leaf, 3) satisfies bst-inv bst-insert(b1, 1) satisfies bst-inv bst-insert(b1, 3) satisfies bst-inv bst-insert(b2, 2) satisfies bst-inv bst-insert(b2, 4) satisfies bst-inv bst-insert(b2, 9) satisfies bst-inv bst-insert(b3, 22) satisfies bst-inv bst-insert(b3, 7) satisfies bst-inv bst-insert(b3, -1) satisfies bst-inv bst-insert(b3, 6) satisfies bst-inv end #| 6. Define a new data type, TwoThreeTree, that is like a binary tree but allows each node to have exactly two or three children rather than exactly two. Like BSTree, TwoThreeTree should contain values at the nodes rather than at the leaves. You can read more about TwoThreeTrees here: https://en.wikipedia.org/wiki/2%E2%80%933_tree. As an example, here is a graphical representation of one possible TwoThreeTree: 3 / \ 1,2 5 / | \ / \ l l l l l where "l" stands for leaf. Nodes with two children, like the root node 3, have one value. Nodes with three children, like 1,2, have two values. |# #| FILL IN YOUR DEFINITION OF data TwoThreeTree HERE ... |#