1. Naming and Scope (15 points)

As seen in the lecture on Naming and Scoping (slides 9-8 through 9-15 and 9-25 through 9-26 in these slides), (1) the same name can be declared in several different scopes and (2) names can be consistenly renamed as long as they do not changed the “connection” between references and declarations. In this problem you will rename each declaration of a given name by suffixing the name with distinct “subscripts” that begin with an underscore, and consistently rename all uses to maintain the meaning of the program. For example, given the following set of definitions

(7 points) The following contrived collection of Racket definitions can be found here. Create a copy of this file named yourAccountName-scoping.rkt. In this file, perform consistent renaming with subscripted names as described above.

The original top-level definiton of f did not make sense and has been fixed.

Although this problem has many details, once you understand the scoping rules, it should be straightforward.

Please use textual subscripts that are easy to understand.

You may use Dr. Racket’s feature for highlighting the connections between declarations and references to help you check your solution, but should not use it to initially create your solution.

(8 points) The following contrived collection of Python definitions can be found here. Create a copy of this file named yourAccountNameScoping.py. In this file, perform consistent renaming with subscripted names as described above.

In this problem, you will be experimenting with the second interpreter. Begin by making a copy of ps5-postfix.rkt named yourAccountName-ps5-postfix.rkt and load this into Dr. Racket. Near the bottom of this file is the following PostFix program named sos (for “sum of squares”). Racket’s semi-colon comments have been used to explain the commands in the program:

;; Sum-of-squares program (define sos'(postfix2; let's call the arguments a and b, from top down1nget; duplicate a at top of stackmul; square aswap; stack now has b and a^2 from top down1ngetmul; square badd; add b^2 + a^2 and return))

Do not modify postfix-exec-command for this part. Instead, just add three bindings to the list postfix-relops.

The testing function (test-3b) will test all of le, ge, and and in the context of the single PostFix program test-sorted:

(define test-sorted'(postfix3; let's call the arguments a, b, and c, from top down2ngetle; is a <= b?3nget3ngetge; is c >= b?and ; is a <= b and c >= b?))>(test-3b); Uses the test-sorted program in its definition Tryingtest-sortedon(456):worksasexpected; result = 1Tryingtest-sortedon(455):worksasexpected; result = 1Tryingtest-sortedon(445):worksasexpected; result = 1Tryingtest-sortedon(444):worksasexpected; result = 1Tryingtest-sortedon(465):worksasexpected; result = 0Tryingtest-sortedon(564):worksasexpected; result = 0Tryingtest-sortedon(546):worksasexpected; result = 0Tryingtest-sortedon(654):worksasexpected; result = 0Tryingtest-sortedon(645):worksasexpected; result = 0Tryingtest-sortedon(554):worksasexpected; result = 0Tryingtest-sortedon(544):worksasexpected; result = 0

(5 points) Extend PostFix with a dup command that duplicates the top element of the stack (which can be either an integer or executable sequence). For example:

Test your dup implementation using the above test cases. Your dup implementation should ensure there is at least one value on the stack, and give an appropriate error message when there isn’t.

(10 points) In this part you will extend PostFix with a rot command that behaves as follows. The top stack value should be a positive integer rotlen that we’ll call the rotation length. Assume there are n values v1, …, vn below rotlen on the stack, where n is greater than or equal to rotlen. Then the result of performing the rot command is to rotate the top rotlen elements of the stack by moving v1 after vrotlen. I.e., the order of values on the stack after rot should be v2, …, vrotlen, v1, vrotlen+1, …, vn. So the first rotlen elements of the stack have been rotated by one unit, while the order of the remaining elements on the stack is unchanged.

Test your rot implementation using the above test cases. Your rot implementation should give appropriate error messages in various situations, like those in the test cases.

4. Pair Generation Revisited (20 points)

In this problem, you will translate Racket functions that are solutions to the pair generation problem (Problem 4) of PS4 into corresponding SML functions. Since you are already given the implementations, your focus is on SML syntax and types rather than on algorithms.

If you haven’t done so already, read these notes on SML/NJ and Emacs SML Mode and follow the advice there. In particular, it is strongly recommended that you create an SML interpreter within a *sml* buffer in Emacs. Then you can use M-p and M-n to avoid retyping your test expressions.

Because hyphens are not allowed in SML identifiers, you should translate all hyphens in Racket identifiers to underscores. E.g., pairs-hof in Racket becomes pairs_hof in SML.

Be careful with your explicit parenthesis in SML. Many type errors in SML programs come from incorrectly parenthesizing expressions.

Racket’s append translates to SML’s infix @ operator, but when you want to pass it as an argument to a first-class function you write it as op@.

In this entire problem (not just this part) some instances of Racket’s cons will translate to SML’s infix list-prepending operator ::, while some will translate to the tupling notation (<exp1>,<exp2>) for pair creation. Reason about SML types to figure out which to use when. SML’s type checker will yell at you if you get it wrong.

Control.Print.printLength controls how many list elements are displayed; after this number, ellipses are used. For example:

SML does not allow ? in identifiers, so translate done? to is_done or isDone

Use pattern matching on tuples when translating the (λ(pair)...) functions. Translate these to something like (fn(fst,snd)=> ...).

pairs_iter (5 points) Here is a version of the Racket pairs-iter definition from the PS4 Problem 4d solutions in which the pairs-diff-tail and pairs-start-tail helper functions are defined internally rather than a top level. This allows omitting some paremeters in their definitions:

For simplicity, we will only consider 2-3 trees of integers, though they can be generalized to handle any type of value.

For conciseness in constructing and displaying large trees, the three constructors have single-letter names. For example, below are the pictures of four sample 2-3 trees from the handout and how they would be expressed with these constructors:

The height property (called path length invariant in the handout): in a 2-node or 3-node, the height of all subtrees must be exactly the same.

The height property guarantees that a valid 2-3 tree is balanced. Together, these two properties guarantee that a 2-3 tree is efficiently searchable.

The file TTTree.sml contains the TTTree dataytype defined above as well as numerous examples of valid and invalid 2-3 trees that are instances of this datatype. Note that t and vt are used for valid trees, io is used for invalidly ordered trees, and ih is used for invalid height trees.

In this problem you will (1) implement a validity test for 2-3 trees and (2) implement the effcient 2-3 insertion algorithm from the handout on 2-3 trees.

Begin by copying the starter file TTTreeFuns.sml and flesh out the missing definitions as specified below. (At the end you can rename it to yourAccountName-TTTreeFuns.sml, but there’s no need to do that yet.)

satisfiesOrderingProperty (7 points)

In this part, you will implement a function satisfiesOrderingProperty:TTTree->bool that takes an instance of the TTTree datatype and returns true if it satisfies the 2-3 tree ordering property and false if it does not. An easy way to do this is to define two helper functions:

elts:TTTree->intlist returns a list of all the elements of the tree in in-order — i.e.,

In a 2-node with left subtree l, value X, and right subtree r, all values in l are listed before X, which is listed before all elements in r.

In a 3-node with left subtree l, left value X, middle subtree l, right value Y, and right subtree r, all values in l are listed before X, which is listed before all values in m, which are listed before Y , which is listed before all values in r.

isSorted:intlist->bool returns true if the list of integers is in sorted order and false otherwise.

to load the TTTree datatype and example trees. Then every time you change the file TTTreeFuns.sml, execute

use"TTTreeFuns.sml"

If you haven’t done so already, read these notes on SML/NJ and Emacs SML Mode and follow the advice there. In particular, it is strongly recommended that you create an SML interpreter within a *sml* buffer in Emacs. Then you can use M-p and M-n to avoid retyping your test expressions.

You may need to execute Control.Print.printLength:=100; in order to see all the list elements.

Implement isSorted using the same zipping approach you used in PS3. You do zipping in SML via ListPair.zip from the ListPair module. List.all from the List module is also helpful.

satisfiesHeightProperty (6 points)

In this part, you will implement a function satisfiesHeightProperty:TTTree->bool that takes an instance of the TTTree datatype and returns true if it satisfies the 2-3 tree height property and false if it does not. An easy way to do this is to define satisfiesHeightProperty as

funsatisfiesHeightPropertyt=Option.isSome(heightt)

where Option.isSome is a function from the Option module that determines if an option value is a SOME (vs. a NONE) and height is a function with type TTTree->optionint such that heightt returns SOMEh if t satisfies the height property with height h and otherwise returns NONE.

insert (12 points) In this part, you will implement the insertion algorithm for 2-3 trees as described on pages 3 to 5 of the handout on 2-3 trees. You will not implement the special cases described on page 6 .

The insertion algorithm is a recursive algorithm that uses the notion of a pseudo 2-node that is “kicked up” in certain cases and handled specially in the upward phase of the recursion. To distinguish an insertion result that is a regular 2-3 tree from a pseudo 2-node that is “kicked up”, we use the following datatype:

The ins function should only call ins recursively and should not call insert.

An easy way to test insert is to call the following listToTTTree function on a list of numbers generated by range. (These are already defined in your starter file.) Note that you may need to increase the print depth (via Control.Print.printDepth:=100) in order to see all the details of the printed trees:

Unfortunately, inserting elements in sequential order as above tends to create 2-3 trees with almost all 2-nodes and very few 3-nodes. It turns out a better way is to mix up the order of insertion as is done in the following code for makeTTTree. This results in trees that have a good mix of 2-nodes and 3-nodes:

(* Return list that has all ints in nums, but those not divisible by 3 come before those divisible by three *)funarrangeMod3nums=letval(zeroMod3,notZeroMod3)=List.partition(fnn=>(nmod3)=0)numsinnotZeroMod3@zeroMod3end(* Make a 2-3 tree with elements from 1 up to and including size. Use arrangeMod3 to mix up numbers and lead to more 3-nodes than we'd get with sorted integers lists *)funmakeTTTreesize=listToTTTree(arrangeMod3(range1(size+1)))-makeTTTree17;valit=H(W(W(L,1,L),2,H(L,3,L,4,L)),5,W(H(L,6,L,7,L),8,H(L,9,L,10,L)),11,H(H(L,12,L,13,L),14,W(L,15,L),16,W(L,17,L))):TTTree-makeTTTree44;valit=W(W(W(W(W(L,1,L),2,H(L,3,L,4,L)),5,W(H(L,6,L,7,L),8,H(L,9,L,10,L))),11,W(W(H(L,12,L,13,L),14,H(L,15,L,16,L)),17,W(H(L,18,L,19,L),20,H(L,21,L,22,L)))),23,W(W(W(H(L,24,L,25,L),26,H(L,27,L,28,L)),29,W(H(L,30,L,31,L),32,H(L,33,L,34,L))),35,W(W(H(L,36,L,37,L),38,H(L,39,L,40,L)),41,W(W(L,42,L),43,W(L,44,L))))):TTTree

Finally, to test your insertion implementation more extensively, try (testInsert1000) with the following function, which will use makeTTTree to create 2-3 trees containing 0 elements to 1000 elements and warn you of any invalid trees.

funtestInsertupToSize=letvalpairs=map(fnn=>(n,makeTTTreen))(range0(upToSize+1))val(validPairs,invalidPairs)=List.partition(fn(_,t)=>isValidt)pairsvalwrongElts=List.filter(fn(n,t)=>(eltst)<>(range1(n+1)))validPairsinif(nullinvalidPairs)andalso(nullwrongElts)then(print"Passed all test cases\n";[])elseif(not(nullinvalidPairs))then(print"There are invalid trees in the following cases\n";invalidPairs)else(print"The elements or element order is wrong in the following cases\n";wrongElts)end-testInsert1000;Passedalltestcasesvalit=[]:(int*TTTree)list

When you are done with this problem, rename TTTreeFuns.sml to yourAccountName-TTTreeFuns.sml before submitting your work.

Extra Credit: PostFix Iterations (15 points)

(5 points) Study and test the following mystery PostFix program of one argument, which is provided near the end of the file. Describe the function that it calculates on its one argument, and give a brief, high-level description of how it calculates that function.

;; What function does this compute? (define mystery'(postfix11(swapdup0eq(swap)(swap2ngetmulswap1subswap3ngetexec)selexec)3rot3ngetexec))

(10 points) Write a PostFix program that takes a single argument (assumed to be a nonnegative integer n) and iteratively calculates the nth Fibonacci number.

Extra Credit: 2-3 Tree Deletion (20 points)

In SML, implement and test the 2-3 tree deletion algorithm presented in pages 8 through 11 of the handout on 2-3 trees.

Extra Credit: 2-3 Trees in Java (35 points)

Implement and test 2-3 tree insertion and deletion in Java. Compare the SML and Java implementations. Which features of which languages make the implementation easier and why?