Assuming you took CS5, at this point in CS60 youve worked with (at least) four programming languages: Python, HMMM, Racket, and now Prolog. This week - and in the coming weeks - we will add Java to that list (if it's not already there).
In addition, over the next two weeks you'll build your own task-specific language that will be able to handle Unicalc-like conversions -- this time using Java, instead of Racket.
This week you'll use or build several Java classes, i.e., custom data structures. The largest one, named OpenList will be the backbone for a Java implementation of unicalc, which you'll complete next week. With the OpenList class you'll create a Racket-like linked list that can be naturally handled recursively. Thus, OpenList "shows off" Java's data-structuring capabilities without sacrificing the generality and power of Racket and Scheme's functional style.
Problems 1 should be completed individually; the other problems may be completed individually or in pairs. If you do work in a pair, please be sure both partners submit the file to their own submissions account; each should include a hash, e.g., #partnername in the pair-programming textbox -- this will help the graders give the same grade to both.
Overall, you will submit 3 files for this week's three problems:
This problem should be completed individually.
20 points
Java has eight built-in (primitive) types -- the most common are such as int, char, boolean, and double. It has thousands of class types in its libraries.
Yet one class type that Java does not have is
Complex, representing the complex numbers commonly used by
mathematicians, scientists, engineers, physicists, and Math 35 students.
This problem asks you to implement a complex-number data structure (class),
Complex.java. You should start with the provided file:
Complex.java.
The Point class example we developed in class will be a good reference. After all, 2d points and Complex numbers are not so different! The complete Point class is available here in Point.java.
Testing your code! Just as in Scheme, you should test each of your methods (functions) as you write it. To do this in Java, uncomment the lines in the provided Complex class's main method that test each of your methods. For this assignment, we will check visually that the answers are what they should be, so feel free to do the same.
It's much easier to make small syntactic errors in Java than in Scheme - there is simply more code where errors can appear! To help with this, the starter file provides lots of tests in a comment in main. As you implement your Java methods, uncomment the appropriate tests and try them out!
Complex number notes Recall that a complex number is of the form a + bi where a and b are real numbers and i is an imaginary number. Addition of two complex numbers is defined by (a + bi) + (c + di) = (a+c) + (b+d)i. Similarly, to multiply two complex numbers, we simply compute (a + bi) * (c + di) = ac + adi + bci + bdii = (ac - bd) + (ad + bc) i since ii (i times i) is -1 by definition.
In your implementation, the numbers a and b in the complex number a + bi should be represented by doubles: these will be the two data members of your class. Please use the names real and imag (as already appear in the starter file) for these data members -- this will be far more readable/memorable than a and b!
Below are the public methods (functions) which your Complex class should contain. Be sure to use exactly these names and the specified kinds of arguments and return values. You may use any additional private methods of your own design, though such helper functions are not required.
public Complex( double real_in, double imag_in )
public Complex( )
// method: toString
// in: no inputs
// out: the String version of this Complex object
public String toString()
{
String sign = "+"; // default sign is plus
if (this.imag < 0.0)
sign = "-"; //
return "" + this.real + " " + sign + " " + Math.abs( this.imag ) + "i";
}
// method: equals
// in: any object o
// out: true if o is Complex and its contents
// match this's contents
public boolean equals(Object o)
{
if (o instanceof Complex)
{
Complex c = (Complex)o; // cast o to a Complex
// now we can access c's imaginary and real parts...
return c.imag == this.imag && c.real == this.real;
}
else
{
return false;
}
}
public Complex add( Complex c )
public Complex negate( )
public Complex conjugate( )
public Complex multiply( Complex c )
To divide one complex number by another, first multiply both the numerator and denominator by the conjugate of the denominator. That ensures that the denominator is a real value! Then, you can simply divide both the real and imaginary components of the numerator by that value.
Here is the method signature:
public Complex divide( Complex c )
Be sure to test out your Complex class with the tests provided in main before submitting your Complex.java file.
Pair or individual
20 points
At this point you've written code for Binary
Search Trees (BSTs) in two different languages: Racket & Prolog.
Here, we provide a binary-search-tree data structure (class) in Java
named BSTNode. For this problem, you should start with
this almost-complete BSTNode class:
BSTNode.java
BSTNode has much of its basic functionality already provided. This provides both an additional example of how Java creates data structures and it's to allow you to focus on the most algorithmically interesting facets of binary search trees!
In this problem you will add the methods insert and delete for the class BSTNode.
public BSTNode insert(String new_key, Object new_value)
which should return a new tree identical to this tree, but
such that the new_key (with new_value)
are inserted in the new, returned tree in the appropriate spot. In particular,
use the Racket implementation below as a guide:
public BSTNode delete(String key_to_go)
which should return a new tree identical to this invoking tree, except
that the node whose key is key_to_go should be deleted, along with its
value. Similar to the way you did this in Racket, your delete method
should
The BSTNode class is designed to work in the same way as our Racket implementations of binary search trees. Each object of type BSTNode can be considered both a full BST and, at the same time, can be considered a single node within a (BST. This is just like OpenList, where each object of type OpenList is a single node within a Racket-like linked list. The other similarity is the design decision that BSTNodes cannot be modified. Therefore, when you write the methods insert and delete, you should not change the instance variables, i.e., the data members, of any of the BSTNodes in the original tree structure.
For reference, here is a Racket implementation of insert and delete:
#lang racket
(define BT1 '( 42
(20 (7 (1 () ()) (8 () ()))
(31 () (41 () ())))
(100 (60 () ())
()) ) )
(define (make-bst key left right) (list key left right))
(define get-key first)
(define get-left second)
(define get-right third)
(define (leaf? BST) (and (null? (get-left BST)) (null? (get-right BST))))
(define (empty? BST) (null? BST))
;; here is a Racket implementation of insert:
(define (insert new_key oldBST)
(cond ((empty? oldBST)
(make-bst new_key '() '()))
((equal? (get-key oldBST) new_key)
oldBST)
((< (get-key oldBST) new_key)
(make-bst
(get-key oldBST)
(get-left oldBST)
(insert new_key (get-right oldBST))))
(else
(make-bst
(get-key oldBST)
(insert new_key (get-left oldBST))
(get-right oldBST)))))
(define start (make-bst 42 '() '()))
(define v2 (insert 100 (insert 20 start)))
(define v3 (insert 7 (insert 31 (insert 60 v2))))
(define v4 (insert 1 (insert 8 (insert 41 v3))))
;; a test for several inserts...
(equal? BT1 v4)
;; here is a Racket implementation of find-min.
;; This function presumes that BST is NOT EMPTY!
(define (find-min BST)
(if (null? (get-left BST))
(get-key BST) ;; return the root if the L is empty
(find-min (get-left BST)))) ;; else descend on the left...
;; here is a Racket implementation of delete:
(define (delete key_to_go oldBST)
(if (null? oldBST)
'() ;; return the empty tree
(let* (
(root (get-key oldBST))
(L (get-left oldBST))
(R (get-right oldBST))
)
(if (< key_to_go root)
(make-bst root (delete key_to_go L) R)
(if (> key_to_go root)
(make-bst root L (delete key_to_go R))
;; must be the case that key_to_go == root here!
(if (null? L)
R ;; whether or not R is empty, it's what we want
(if (null? R)
L ;; if R is empty, we want L
;; otherwise, we grab the min of the right and make it the root
(make-bst (find-min R) L (delete (find-min R) R)) )))))))
;; tests for delete...
(define BT1_no42 '( 60
(20 (7 (1 () ()) (8 () ()))
(31 () (41 () ())))
(100 ()
()) ) )
(define BT1_no100 '( 42
(20 (7 (1 () ()) (8 () ()))
(31 () (41 () ())))
(60 () ())))
(define BT1_no8 '( 42
(20 (7 (1 () ()) ())
(31 () (41 () ())))
(100 (60 () ())
()) ) )
(equal? (delete 42 BT1) BT1_no42)
(equal? (delete 100 BT1) BT1_no100)
(equal? (delete 8 BT1) BT1_no8)
This problem may be done individually or in a pair. If you work in a pair, be sure to follow the CS department's pair programming practices throughout.
60 points
This problem is meant to introduce you to a larger part of the implementation of data structures in Java. In this case, you will be implementing in Java the "open lists" or Racket-type linked lists that you used in that language through the first few weeks of the course.
Write a java class named OpenList that implements an (open) linked list data structure. Since the goal of this problem is to transition from Racket/Scheme to Java, you will be implementing lists in the same way Racket does. In fact, this problem is, essentially, the first step in writing the Racket programming language in Java.
Open lists are characterized by not permitting modifications to existing data. As a result, users of OpenList can re-use lists many times without worrying about one part of their code changing a value that another part of their program might have needed. This means OpenLists do not permit side-effects: the changing of variables in-place.
Write your class in a file called OpenList.java. You should start with this the OpenList.java file. The main method of this OpenList.java contains a couple of informal tests, and the additional file, MainFromCompletedOpenList.java, has tests that use more of the methods in their testing. Try them!
To begin, you might want to try compiling and running the file to start building intuition about how OpenLists work.
There are three data members we will use
in this OpenList. Here is how the class
starts (this is in the provided OpenList.java
file:
Notice that because the data member named first is of type Object, this OpenList implementation will be able to create lists of any Java Objects. Everything in Java is an Object except the types int, double, float, boolean, byte, char, and long. We will use only Strings and OpenLists for this assignment. The next assignment, on the other hand, will put your OpenList class to work handling more types.
In Java and other object-oriented languages, functions that are members of a class are often called methods.
So, in your OpenList class, implement the following methods. Note that both static and nonstatic versions of the methods are required in many cases. This is not typical -- it is simply to get used to the distinction between the two. Feel free to implement one of the pair and then implement the other based on it.
Several methods are already provided in the starter file - please keep those!
This toString method is a nonstatic method that returns a String representation of the invoking list. A static version isn't needed. The toString method is used internally by Java whenever your code adds a String to an OpenList, e.g., in code like this:
OpenList L = OpenList.list("Hello","World");
System.out.println("List L is " + L);
This code should output
List L is ["Hello", "World"]You will also use this method quite a bit when writing your unit tests, so you can assume it's working correctly.
String m1 = "mudd";Basically, if compareTo is less than zero, then this is before the input argument in the dictionary.
String m2 = "mit";
if ( m2.compareTo(m1) < 0 ) ... // this test will evaluate to true
String firstOfL = (String)L.first(); // we cast the Object returned by first()Thus, you'll want to use lines like these two in order to grab the two strings you'll need to implement the merge function.
String firstOfM = (String)M.first(); // into the String we know it to be...
As the final capability for this week's OpenList class, implement the mergesort sorting routine.
As we have (or will) discuss in class, mergesort works recursively:
So, for this method, write
As a suggestion, our solution used a method named split, which returned an array of two OpenLists, each half the size of the original - or "within 1 element" of half, to be more precise.
Object s = new String("I'm a string.");
if ( s instanceof String ) ... // this test will evaluate to true
if ( s instanceof OpenList ) ... // this test will evaluate to false
No static version is required. Remarkably, Java's compiler checks if an
instanceof expression can possibly
be true - the code won't compile if it can't! In this case, s
is of type Object, which could be
a String or a OpenList.
(The compiler doesn't actually read the code, it
just checks the types....) Testing Again, you will want to run at least the tests provided in the additional file above (you can copy-and-paste those to main).
Submitting Be sure to submit your OpenList.java file to the submission site.