OSU List Stack & Queue Using Nodes & References Instead of Arrays Exercise
In this assignment, we’ll build three classes that will implement a List, a Stack, and a Queue using Nodes and references rather than arrays. The Stack and Queue will inherit from the List (a linked list) class, and so these subclasses will be rather short. Then, build a simple LinkedListException class that will be thrown in place of RuntimeExceptions whenever exceptional or error situations occur. The inheritance hierarchy we are going to implement is depicted below.Note: Throughout this document, we will usually refer to the List implementation using Nodes and references (i.e., as a linked list) as “List”. However, you should not confuse this with the Java built-in interface List, which has the same name. At this point, it won’t be an issue; it’s just something to keep in mind in the future.Another Note: You do NOT need to use generics for this assignment. That's much more difficult. (I.e., you don't have to have something like "Node<T>" -- like they do on pp. 824-828). Instead, you can just have your Node's data (sometimes called 'item') be of type Object. (If you REALLY want to do generics, you can.)The List ClassIn this section, we describe the List data structure in detail. Once we've built a List superclass, building two new classes that extend list will be trivial in comparison.DataNode head = null; //the start of the linked listUse no other instance variables here in the List class (the phrase "use no other instance variables" does not refer to the inner class Node, which is structured as we saw in Savitch): No int size or Node tail, etc.You probably will implement Node as an inner class (it's a really good idea to).Methodspublic void insert(Object newData, int index) (15-30 LOC):Make a new node that holds the Object “newData”, and insert this at the position specified by indexDivide this method into sections/cases using an if:If adding to an empty list (changes head) { }else if adding to a single element list (changes head) {}else if adding to a populated list (n>=2)In each case above, note that the following subcases exist:Adding to the head of the list (changes head)Adding to the body or the tail of the list (not head)The cases above exist because of the increased complexity involved when implementing a linked list, but essentially reduce to determining “do I change head? Or a node indirectly attached to head?” Note that not all subcases apply to each outer case; for example, you don’t worry about adding to the body or tail of a list if you're adding to an empty list (the first outer case). Read chapter 15 for more details on this.Another way to think about this, (instead of thinking about whether your list is empty) is: case 1: I'm inserting at index 0 (in which case I need to change the head)case 2: I'm inserting at some index > 0, in which case I need to change some other node past head and have it point at the new Node.Question: "What should I have my insert method do if a user tries to, say, insert at index position 10, when the List is only 5 elements long?" Answer: throw a LinkedListException exception and don't insert anything. (If the List is 5 elements long and they try to insert at position 5, then you can add it onto the end without an exception.)public Object remove(int index) (15-30 LOC):Find and delete the node at the position specified by indexNote that this method, like add() above, has the following cases:Deleting on an empty listDeleting on a single element list (head only)Deleting on a populated list (n>=2)And, just like above, there are the following subcases:Deleting the head of the list (changes head)Deleting from the body or tail of the list (no changes to head)The cases above exist due to the complexity of correctly managing a list of nodes and a head pointer. Note your software will not provide for all (3*2==6) cases, since some subcases don’t apply to their enclosing (outer) case. For example, if you’re deleting from a single element list (head only), then you don’t have to worry about deleting from the body or tail (as this subcase doesn’t exist).public void append(Object newData): Adds newData to the end of the list.public void delete(int index): Note that delete is the same as remove but has no return value.public int size(): A standard accessorpublic String toString(): Enumerate your list and return this enumeration as a String.public boolean isEmpty(): Returns true if empty, false otherwisepublic int indexOf(Object target): A sequential search that returns the index if found or -1 if not foundpublic boolean equals(Object other): this should check if one list is a deep copy of the other. (Note: this method will be inherited by the Stack and Queue subclasses. Does the equals method need to be overridden for them? If it does, then do that. If not, don't.)The Stack SubclassIn this section, we’ll build a structure that functions like a Stack externally, but is built on top of nodes and pointers internally. Since we’ll be extending the List superclass, we only have to provide a few functions (push, pop) that redirect to superclass functions(add, remove) and so this class will be quite short. When you’ve finished this class, test it using driver software.DataNone when inheriting from List.Methodspublic boolean isEmpty(): Returns true if empty, false otherwisepublic void push(Object next):Inserts to the beginning of the listConsider using your superclass methods here rather than implementing pushpublic Object pop():Removes from the beginning of the listConsider using your superclass methods here rather than building pop by handpublic static void main(String[] args): A driver to test your stack – see below for more information.The Queue SubclassNow, we’ll turn our attention to building a Queue. Remember when we used to cut and paste code and logic, then make subtle changes to it to build our new class? Instead, we’ll use inheritance here, automate the copy-and-paste, and just focus on implementing the few methods that differ in our subclass (enqueue(), dequeue()). When you’re done with this class, test it using driver software.DataNone when inheriting from List.Methodspublic boolean isEmpty(): Returns true if empty, false otherwisepublic void enqueue(Object next): Inserts to the beginning of the listpublic Object dequeue(): Removes from the end of the listpublic static void main(String[] args): A driver to test your stack – see below for more information.Stack and Queue Methods to OverrideWhat happens when you inherit from a class, but you only want 70% of the parent class functionality? Sometimes you can inherit and override to address this, which we will do here, but do note that this is a warning flag and should make you investigate other design options instead. Its known that our use of inheritance here is somewhat hackneyed - the “is a” relationship is being stretched almost to a breaking point with our use of Stacks and Queues which are Lists. Even conceptually, we can all agree that a Stack is not a List and then some (complete inheritance), but rather a List with limitations (partial inheritance). We’ll address this with an industry grade solution (Collections in Java) later in this class, but for today, we need to add some “hacks” to our code to block the client from using our Stacks & Queues incorrectly. Whenever you’re hacking rather than adding, you’re violating a major Object-Oriented design principle, but we’ll accept these limitations for today and notice how quickly one can build a Stack or Queue given a List to start from. This reuse can be accomplished using Composition here; another strong alternative to overfitting inheritance. To “fix” our subclasses, we must:Override insert() from the superclass to simply call push() or enqueue() without using the index.Override remove() from the superclass to call pop() or dequeue() without using the index.Do we need to override isEmpty()? indexOf()? Anything else?In cases where parent methods cannot or should not work in the child class and cannot be redirected to a child class' analogue (e.g., delete() in Stack, insert() in Stack and Queue, size() in Stack and Queue), you should override the parent method to make it throw an exception. (You may have to go back and change the List class so it declares the exception class.) This will prevent the user of the child class from improperly using a parent-class method that shouldn't exist for the child class' ADT.The LinkedListException ClassThis class should be under 15 lines of executable code and is an exercise in inheriting from classes provided to you by the Java API. Your class should have only two methods (both constructors) and no data items; see the slides on exceptions for an example of such a class. Throw this exception when an error or exceptional situation occurs in your code, such as a pop() on an empty stack.Building Drivers to Test Your Data StructuresWrite a main driver function in each of your three List, Stack, and Queue classes. Here is an incomplete main that might be included as a driver to begin testing the List class:public static void main(String[] args) {
List empty = new List();
List one = new List();
List multiple = new List();
one.append(5);
multiple.append(10);
multiple.append(20);
multiple.append(30);
System.out.println("Empty:"+empty); // ( note the implicit call to toString()! )
System.out.println("One:"+one);
System.out.println("Multiple:"+ multiple);
one.delete(0);
multiple.delete(1);
System.out.println("One (upon delete):"+one);
System.out.println("Multiple (upon delete):"+ multiple);
//one.insert(600, 1);
multiple.insert(400, 2);
System.out.println("One (on insert):"+one);
System.out.println("Multiple(on insert):"+ multiple);
}
This example, however, is just a starting point. You’ll want to add additional tests. Below is a list of some more tests you can write for each of the data structures. Note that this list is not exhaustive and I expect you to write additional tests beyond these. In general, all methods in a class should have tests written for them:Make a main in the list class:Test inserts for each case outlined in the insert and remove methods above, to be sure each section of logic works in these more complex functions.Add a test that attempts to remove elements from an empty list.Add a test that attempts to remove elements past the size of the list.Make a main in the stack class:Add a test that demonstrates pushes and pops actually reverse the order of the stored elements.Add a test that attempts to pop elements from an empty list.Make a main in the queue class:Add a test that demonstrates the FIFO ordering principle of queues.Add a test that attempts to dequeue elements from an empty queue.DeliverablesYou must build at least 4 classes (List, Stack, Queue, LinkedListException). Use these names exactly, please. If you submit multiple versions, Canvas will rename your files with a "-1", etc. suffix, but that's okay. We can handle that change. But the base name of your class has to be as described.For each data structure class, you must build a driver to test your data structure (see above)Consider common cases and unusual, corner cases.For instance, what if your Stack, Queue, List is empty and I call remove() or pop()?Reminders and HintsYou should, as always, follow all the class Coding Style Guidelines.Commenting should be profuse yet concise, and accurately and usefully define your interfaces.The coding practices we have already covered should be practices you engage in, even if the assignment does not specifically specify it as a requirement. Examples include: enforcement of class invariants; comprehensive testing of classes; handling of edge cases; ability to handle all characteristics of an abstract data, e.g., infinite elements in a stack; etc. Even if the assignment does not specify it as a requirement, I reserve the right to deduct points if they are missing, because they are now implicit expectations for your code.Make sure you comment all methods and the class with javadoc comments. If you have files based on code someone else has written (e.g., is based on a skeleton), you have to javadoc comment the methods someone else wrote also. You cannot receive full-credit for the rubric's commenting criterion if you do not have a javadoc comment for every method, no matter how small. Your code should not depend on absolute path specifications or any other environment-specific specifications. The program should not ask for user input from the console.Don't worry about potential privacy leaks in this homework. Dealing with them when you have Object class input is tricky.Note that the List class in this assignment is more like an ArrayList than the list prototype ADT we've talked about in lecture.Also, note that you have to build Stack and Queue so that they inherit from List. Thus, for instance, do not just use the linked data structure implementation of a stack found in Savitch 15.4.In the section "Stack and Queue Methods to Override" of the assignment description, some may be confused why it is called "overriding" if you are using insert/remove to create your push/pop/enqueue/dequeue methods in Stack and Queue. What's going on is that you use the parent class (List) version of insert/remove to create push/pop/enqueue/dequeue but then in your child classes (Stack and Queue) you have to override the inherited "copies" of insert/remove with versions that call your child class push/pop/enqueue/dequeue methods because Stack and Queue do not have any methods called insert/remove, etc.In this assignment, we’ll build three classes that will implement a List, a Stack, and a Queue using Nodes and references rather than arrays. The Stack and Queue will inherit from the List (a linked list) class, and so these subclasses will be rather short. Then, build a simple LinkedListException class that will be thrown in place of RuntimeExceptions whenever exceptional or error situations occur. The inheritance hierarchy we are going to implement is depicted below.Note: Throughout this document, we will usually refer to the List implementation using Nodes and references (i.e., as a linked list) as “List”. However, you should not confuse this with the Java built-in interface List, which has the same name. At this point, it won’t be an issue; it’s just something to keep in mind in the future.Another Note: You do NOT need to use generics for this assignment. That's much more difficult. (I.e., you don't have to have something like "Node<T>" -- like they do on pp. 824-828). Instead, you can just have your Node's data (sometimes called 'item') be of type Object. (If you REALLY want to do generics, you can.)The List ClassIn this section, we describe the List data structure in detail. Once we've built a List superclass, building two new classes that extend list will be trivial in comparison.DataNode head = null; //the start of the linked listUse no other instance variables here in the List class (the phrase "use no other instance variables" does not refer to the inner class Node, which is structured as we saw in Savitch): No int size or Node tail, etc.You probably will implement Node as an inner class (it's a really good idea to).Methodspublic void insert(Object newData, int index) (15-30 LOC):Make a new node that holds the Object “newData”, and insert this at the position specified by indexDivide this method into sections/cases using an if:If adding to an empty list (changes head) { }else if adding to a single element list (changes head) {}else if adding to a populated list (n>=2)In each case above, note that the following subcases exist:Adding to the head of the list (changes head)Adding to the body or the tail of the list (not head)The cases above exist because of the increased complexity involved when implementing a linked list, but essentially reduce to determining “do I change head? Or a node indirectly attached to head?” Note that not all subcases apply to each outer case; for example, you don’t worry about adding to the body or tail of a list if you're adding to an empty list (the first outer case). Read chapter 15 for more details on this.Another way to think about this, (instead of thinking about whether your list is empty) is: case 1: I'm inserting at index 0 (in which case I need to change the head)case 2: I'm inserting at some index > 0, in which case I need to change some other node past head and have it point at the new Node.Question: "What should I have my insert method do if a user tries to, say, insert at index position 10, when the List is only 5 elements long?" Answer: throw a LinkedListException exception and don't insert anything. (If the List is 5 elements long and they try to insert at position 5, then you can add it onto the end without an exception.)public Object remove(int index) (15-30 LOC):Find and delete the node at the position specified by indexNote that this method, like add() above, has the following cases:Deleting on an empty listDeleting on a single element list (head only)Deleting on a populated list (n>=2)And, just like above, there are the following subcases:Deleting the head of the list (changes head)Deleting from the body or tail of the list (no changes to head)The cases above exist due to the complexity of correctly managing a list of nodes and a head pointer. Note your software will not provide for all (3*2==6) cases, since some subcases don’t apply to their enclosing (outer) case. For example, if you’re deleting from a single element list (head only), then you don’t have to worry about deleting from the body or tail (as this subcase doesn’t exist).public void append(Object newData): Adds newData to the end of the list.public void delete(int index): Note that delete is the same as remove but has no return value.public int size(): A standard accessorpublic String toString(): Enumerate your list and return this enumeration as a String.public boolean isEmpty(): Returns true if empty, false otherwisepublic int indexOf(Object target): A sequential search that returns the index if found or -1 if not foundpublic boolean equals(Object other): this should check if one list is a deep copy of the other. (Note: this method will be inherited by the Stack and Queue subclasses. Does the equals method need to be overridden for them? If it does, then do that. If not, don't.)The Stack SubclassIn this section, we’ll build a structure that functions like a Stack externally, but is built on top of nodes and pointers internally. Since we’ll be extending the List superclass, we only have to provide a few functions (push, pop) that redirect to superclass functions(add, remove) and so this class will be quite short. When you’ve finished this class, test it using driver software.DataNone when inheriting from List.Methodspublic boolean isEmpty(): Returns true if empty, false otherwisepublic void push(Object next):Inserts to the beginning of the listConsider using your superclass methods here rather than implementing pushpublic Object pop():Removes from the beginning of the listConsider using your superclass methods here rather than building pop by handpublic static void main(String[] args): A driver to test your stack – see below for more information.The Queue SubclassNow, we’ll turn our attention to building a Queue. Remember when we used to cut and paste code and logic, then make subtle changes to it to build our new class? Instead, we’ll use inheritance here, automate the copy-and-paste, and just focus on implementing the few methods that differ in our subclass (enqueue(), dequeue()). When you’re done with this class, test it using driver software.DataNone when inheriting from List.Methodspublic boolean isEmpty(): Returns true if empty, false otherwisepublic void enqueue(Object next): Inserts to the beginning of the listpublic Object dequeue(): Removes from the end of the listpublic static void main(String[] args): A driver to test your stack – see below for more information.Stack and Queue Methods to OverrideWhat happens when you inherit from a class, but you only want 70% of the parent class functionality? Sometimes you can inherit and override to address this, which we will do here, but do note that this is a warning flag and should make you investigate other design options instead. Its known that our use of inheritance here is somewhat hackneyed - the “is a” relationship is being stretched almost to a breaking point with our use of Stacks and Queues which are Lists. Even conceptually, we can all agree that a Stack is not a List and then some (complete inheritance), but rather a List with limitations (partial inheritance). We’ll address this with an industry grade solution (Collections in Java) later in this class, but for today, we need to add some “hacks” to our code to block the client from using our Stacks & Queues incorrectly. Whenever you’re hacking rather than adding, you’re violating a major Object-Oriented design principle, but we’ll accept these limitations for today and notice how quickly one can build a Stack or Queue given a List to start from. This reuse can be accomplished using Composition here; another strong alternative to overfitting inheritance. To “fix” our subclasses, we must:Override insert() from the superclass to simply call push() or enqueue() without using the index.Override remove() from the superclass to call pop() or dequeue() without using the index.Do we need to override isEmpty()? indexOf()? Anything else?In cases where parent methods cannot or should not work in the child class and cannot be redirected to a child class' analogue (e.g., delete() in Stack, insert() in Stack and Queue, size() in Stack and Queue), you should override the parent method to make it throw an exception. (You may have to go back and change the List class so it declares the exception class.) This will prevent the user of the child class from improperly using a parent-class method that shouldn't exist for the child class' ADT.The LinkedListException ClassThis class should be under 15 lines of executable code and is an exercise in inheriting from classes provided to you by the Java API. Your class should have only two methods (both constructors) and no data items; see the slides on exceptions for an example of such a class. Throw this exception when an error or exceptional situation occurs in your code, such as a pop() on an empty stack.Building Drivers to Test Your Data StructuresWrite a main driver function in each of your three List, Stack, and Queue classes. Here is an incomplete main that might be included as a driver to begin testing the List class:public static void main(String[] args) {
List empty = new List();
List one = new List();
List multiple = new List();
one.append(5);
multiple.append(10);
multiple.append(20);
multiple.append(30);
System.out.println("Empty:"+empty); // ( note the implicit call to toString()! )
System.out.println("One:"+one);
System.out.println("Multiple:"+ multiple);
one.delete(0);
multiple.delete(1);
System.out.println("One (upon delete):"+one);
System.out.println("Multiple (upon delete):"+ multiple);
//one.insert(600, 1);
multiple.insert(400, 2);
System.out.println("One (on insert):"+one);
System.out.println("Multiple(on insert):"+ multiple);
}
This example, however, is just a starting point. You’ll want to add additional tests. Below is a list of some more tests you can write for each of the data structures. Note that this list is not exhaustive and I expect you to write additional tests beyond these. In general, all methods in a class should have tests written for them:Make a main in the list class:Test inserts for each case outlined in the insert and remove methods above, to be sure each section of logic works in these more complex functions.Add a test that attempts to remove elements from an empty list.Add a test that attempts to remove elements past the size of the list.Make a main in the stack class:Add a test that demonstrates pushes and pops actually reverse the order of the stored elements.Add a test that attempts to pop elements from an empty list.Make a main in the queue class:Add a test that demonstrates the FIFO ordering principle of queues.Add a test that attempts to dequeue elements from an empty queue.DeliverablesYou must build at least 4 classes (List, Stack, Queue, LinkedListException). Use these names exactly, please. If you submit multiple versions, Canvas will rename your files with a "-1", etc. suffix, but that's okay. We can handle that change. But the base name of your class has to be as described.For each data structure class, you must build a driver to test your data structure (see above)Consider common cases and unusual, corner cases.For instance, what if your Stack, Queue, List is empty and I call remove() or pop()?Reminders and HintsYou should, as always, follow all the class Coding Style Guidelines.Commenting should be profuse yet concise, and accurately and usefully define your interfaces.The coding practices we have already covered should be practices you engage in, even if the assignment does not specifically specify it as a requirement. Examples include: enforcement of class invariants; comprehensive testing of classes; handling of edge cases; ability to handle all characteristics of an abstract data, e.g., infinite elements in a stack; etc. Even if the assignment does not specify it as a requirement, I reserve the right to deduct points if they are missing, because they are now implicit expectations for your code.Make sure you comment all methods and the class with javadoc comments. If you have files based on code someone else has written (e.g., is based on a skeleton), you have to javadoc comment the methods someone else wrote also. You cannot receive full-credit for the rubric's commenting criterion if you do not have a javadoc comment for every method, no matter how small. Your code should not depend on absolute path specifications or any other environment-specific specifications. The program should not ask for user input from the console.Don't worry about potential privacy leaks in this homework. Dealing with them when you have Object class input is tricky.Note that the List class in this assignment is more like an ArrayList than the list prototype ADT we've talked about in lecture.Also, note that you have to build Stack and Queue so that they inherit from List. Thus, for instance, do not just use the linked data structure implementation of a stack found in Savitch 15.4.In the section "Stack and Queue Methods to Override" of the assignment description, some may be confused why it is called "overriding" if you are using insert/remove to create your push/pop/enqueue/dequeue methods in Stack and Queue. What's going on is that you use the parent class (List) version of insert/remove to create push/pop/enqueue/dequeue but then in your child classes (Stack and Queue) you have to override the inherited "copies" of insert/remove with versions that call your child class push/pop/enqueue/dequeue methods because Stack and Queue do not have any methods called insert/remove, etc.