Lab 06 - Railroad
(10/31/2022)
References
FAQ
Description
Create a railroad train station that uses a central turntable and roundhouses to store and order train cars. Cars enter the train station if the turntable is empty. From the turntable cars can be moved to a "stack" roundhouse facility (LIFO) or a "queue" roundhouse facility (FIFO). Train cars exit the train station by moving from the turntable to a "vector" of outbound cars.
Learning Outcomes
By completing the Railroad Lab, you will be able to:
- Design and implement a Deque container with constant time operations using a dynamic circular array.
- Design and implement a Vector class that has-a ADT deque.
- Design and implement a Queue class that has-a ADT deque.
- Design and implement a Stack class that has-a ADT deque.
- Design and implement a Station class that has-a Vector, Queue, and Stack.
Discussion
The Deque
A deque (also called a double-ended queue) is an abstract data type that generalizes a queue, as elements can be added to or removed from either the front (head) or back (tail). There are two restricted type of deques as well:
- An input-restricted deque (IRDeque) is one where deletion can be made from both ends, but insertion can be made at one end only.
- An output-restricted deque (ORDeque) is one where insertion can be made at both ends, but deletion can be made from one end only.
The deque is an Abstract Data Type and can be implemented in two ways: contiguous and linked. A modified dynamic array that is contiguous and offers constant time (O(1)) operations.
The circular dynamic array approach uses a variant of an array that can grow from both ends These array deques have all the properties of a dynamic array, namely
- constant-time random access,
- good locality of reference,
- and amortized constant-time insertion/removal at both ends, instead of just one end.
Circular Buffer
A deque can be implemented with a dynamic circular array. The array is resized when an item is added to the array and the array is full. This decreases the frequency of resizing’s and amortizes the cost over subsequent additions to the deque.
The C++ standard library defines the class std::deque to be a sequence that, like the std::vector, supports random-access iterators (not supported by either the stack or the queue) in addition to constant-time addition and removal from either end.
Adapter Design Patterns and Wrapper Classes
An adapter pattern converts the public face (interface) of a class into another public face (interface) that the client expects. Adapters let classes work together that have incompatible interfaces.
A real world example would be a USB to Ethernet adapter. We need this adapter when we have an Ethernet interface on one end and USB on the other. Since each has a different communication protocol, an adapter can convert one to the other.
A "wrapper class" is a de facto term meaning a class that "wraps around" a resource, such as another class. An adapter pattern is often implemented by a class (client) wrapping another class (server) and delegating or forwarding member functionality from the client to the server. In other words, delegation refers to evaluating a member (property or method) of one object (the client) in the context of another, original object (the server). In the simplest case, an object that simply uses another object is referred to as "aggregation".
The ADT deque class can be used to implement a vector, stack, or queue using an adapter design pattern. A vector class can wrap a deque class and use the deque's functionality to define the vector class' behavior. Although this approach has its strengths and weaknesses, it definitely simplifies implementation of the vector and enhances code reuse.
In this course, we have been discussing many of the underlying data structures that are used to implement data containers. The ADT deque class can be used to implement a vector, stack, or queue using an adapter pattern.
Consider the following:
#include "Deque.h" template <typename T> class myVector { private: Deque<T> vector_; public: void push_back(T data) { vector_.push_back(data); return; } T& pop_back() { T temp = vector_.back(); vector_.pop_back(); return temp; } ... };
The class myVector can easily be implemented by "wrapping" the Deque class and using the already-existing functions of the deque's underlying data structure, thus saving ourselves a lot of work. Wrapper classes are a very convenient use of abstraction, especially in rapid software development.
Of course, we should care about which type of underlying data structure a container uses and you will make design decisions based on those data structures. For example, a set implemented with a tree structure will have O(log(n)) find-time whereas a set implemented with a hash table will have a O(1) find-time. If find was the most critical function for your set, it would be more efficient to use a set class that wraps a hash table.
Vector
Train cars leaving the station turntable are pushed onto a vector wrapper class.
Queue
Train cars can be warehoused in a queue roundhouse. Cars move to and from the queue and the station turntable.
Stack
Train cars can be warehoused in a stack roundhouse. Cars move to and from the stack and the station turntable.
The Lab
The Station
The Railroad Station is a container facility for railroad cars. Central to the station is a turntable through which all station car movement occurs. The turntable is surrounded by two types of roundhouses: a stack house and a queue house. In addition, train cars exit the turntable to a vector house used to assemble the outgoing train.
The following is a UML diagram of the station objects:
Station Commands
- Add:station adds a train car (a struct containing a nonÂnegative integer) to the station turntable. The car is added to the station turntable if the turntable is empty. Return "OK" if successful, else report an error.
- Remove:station removes the current car on the station turntable and adds the car to an outgoing vector assembly object. Return "OK" if successful, else report an error.
- Add:xxxx removes a car from the station turntable and adds to the designated data structure. Return an error if the turntable is empty, else "OK".
- Remove:xxxx removes a car the designated data structure and adds to the station turntable. Return an error if the turntable is occupied or the data structure is empty, else "OK".
- Top:xxxx displays the current "top" car in the designated data structure. Return an error if the data structure is empty. (Do not remove the car from the data structure.)
- Size:xxxx displays the number of cars in the station designated data structure.
- Find finds the current location of a car in the station data structures. Display the data structure name (or station, vector) and index if found, else error.
- Queue/Stack displays the cars in the station queue/stack roundhouse.
- Train displays the cars in the vector assembly object that have exited the Station.
The following summarizes the commands used to direct railroad car traffic in and out of the railroad station:
COMMAND | DESCRIPTION | OUTPUT |
---|---|---|
Add:station <data>
Add:queue <data> Add:stack <data> |
Train car enters the station turntable.
Train car is removed from the turntable and pushed to the Queue roundhouse. Train car is removed from the turntable and pushed to the Stack roundhouse. |
OK
Turntable empty! Turntable occupied! |
Remove:station Remove:queue Remove:stack |
A train car is removed from the turntable and pushed into the train vector.
A train car is removed from Queue roundhouse and moved to the station turntable. A train car is removed from Stack roundhouse and moved to the station turntable. |
OK
Turntable empty! Turntable occupied! Queue empty! Stack empty! |
Top:station Top:queue Top:stack |
Display the current train car on station turntable,
Display the train car at head of Queue roundhouse. Display the train car at head of Stack roundhouse. |
<data>
Turntable empty! Queue empty! Stack empty! |
Size:queue Size:stack Size:train |
Output number of train cars in Queue/Stack roundhouses and train vector. | Size of Queue
Size of Stack Size of Train |
Queue Stack Train |
Display the contents of the station Queue roundhouse. Display the contents of the station Stack roundhouse. Display the contents of the train vector. |
List of train cars. |
Find <data> | Find and display the current location and position of a car in the station data structures (turntable, queue, stack, or vector). | Turntable
Queue[<index>] Stack[<index>] Train[<index>] Not Found! |
Lab guidelines:
- You may NOT use any predefined data structures from the STL (vector, stack, queue, or deque.) (Don't inherit from an STL container class either!)
- Implement your deque with a single dynamic circular array (OR a dynamic circular array of pointers to multiple smaller fixed-size dynamically allocated arrays).
- Each data structure should be able to store any number of cars.
- A train car is a struct (Car) containing a non-negative integer.
- Instantiate your station template classes (and deque class) as type Car.
- A major objective of this lab is to give you practice with the principle of code reuse. After implementing your deque class, use a has-a pattern to implement your vector, stack, and queue.
Lab requirements:
- Your main program input/output files names come from argv[1] and argv[2], respectively.
- You are required to implement a deque template class that is derived from the DequeInterface class.
- You deque implementation uses a dynamic circular array (OR a modified dynamic circular array) and can store any number of items.
- Begin with a default array size and then double the size for each reallocation.
static const size_t DEFAULT_CAPACITY = 4;
- You are required to implement separate template classes for each data container in the station facility, (i.e. separate classes for vector, stack, and queue) by wrapping your template deque class.
- Each container will be of type Car.
- NO STL CONTAINERS MAY BE USED IN THIS LAB!
Steps to Success:
- Step 1 - Begin with a main function.
- Open input and output files.
- Read through the input files and output the strings.
- Test! You should now have something that works, don't break it!
- Step 2 - Design and implement a Car class (struct).
- Create a struct object named Car in its own .h file.
- The Car class has constructors and contains an unsigned int.
- Include the .h file in your main file.
- Step 3 - Design and implement a Deque template class.
- Create a Deque template class derived from the DequeInterface class.
- Use a dynamic array to contain the typename objects.
- Use a default array size of 4 and resize as needed.
- Temporally instantiate a Deque of type Car in your main.
- Verify your implementation by pushing (push_front and push_back) examining (front, back, and at) and outputting (toString) Car objects
- Test and do not proceed until completed and verified.
- Conditionally remove your test Deque (you might need to test some more later).
- Step 4 - Design and implement Queue, Stack, and Vector classes.
- Create classes for a queue, stack, and vector in their own .h files.
- Make the classes "wrapper classes" that contain your Deque Class. Delegate operations to the Deque Class as required by the container type.
- Do a little research to see what kind of functions each container supports (Stack has push, pop, top, size, etc). You'll find that implementing each function will be pretty straight forward, because all of the functionality will be implemented in the Deque class. You just need to define behavior.
- Again, temporally instantiate a Queue, Stack, and Vector and test their functionality.
- Conditionally remove your test objects.
- Step 5 - Design and implement a Station template class.
- The Station class has-a:
- A station turntable of type typename. (A Car value of 0 could be used to indicate when the turntable is occupied or add an additional boolean turntable occupied variable.)
- A Queue roundhouse container of typename objects.
- A Stack roundhouse container of typename objects.
- A Vector container of typename objects (used for assembling cars as they leave the station).
- Station class methods will correspond to pass-off commands parsed in main.
- The Station class has-a:
- Step 6 - Instantiate a templated Station class in your main.
- Instantiate the Station class as a Car container.
- Write temporary Station methods calls to test station functionality.
- Parse input commands from test files and make appropriate Station method calls.
- Step 7 - Do final integration testing (and test, test, test).
- As you finish each data structure, test it to make sure it works properly. It will be significantly easier to fix a mistake in one place than to code everything and then have to fix that mistake in multiple places in your code.
Helps and Hints
Car struct
- The train station cars are a struct and used to instantiate all containers and the turntable.
Your Car struct might look something like the following:
struct Car { unsigned int id; Car() : id(0) {} ~Car() {} bool operator==(const Car car) { return this->id == car.id; } bool operator!=(const Car car) { return this->id != car.id; } /** Return the Car as a string */ string toString() const { stringstream out; out << id; return out.str(); } // end toString() /** Make insertion operator friend for a Car */ friend std::ostream& operator<< (ostream& os, const Car& c) { os << c.toString(); return os; } // end operator<< };
Circular Dynamic Arrays
- Begin with a default array size and then double the size for each reallocation.
static const size_t DEFAULT_CAPACITY = 4;
- Example Deque constructor:
Deque(void) : capacity(DEFAULT_CAPACITY), num_items(0), front_index(0), rear_index(DEFAULT_CAPACITY - 1), the_data(new T[DEFAULT_CAPACITY]) {}
Finding Memory Leaks.
- Place the following code near the beginning of the file containing your main function:
#ifdef _MSC_VER #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> #define VS_MEM_CHECK _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #else #define VS_MEM_CHECK #endif
- Put the following C pre-processor macro at the beginning of your main code:
int main(int argc, char * argv[]) { VS_MEM_CHECK // Your program... return 0; }
- If you get something like the following when your program exits, YOU HAVE A MEMORY LEAK!
You are required to remove all memory leaks.
The thread 0x34bc has exited with code 0 (0x0). Detected memory leaks! Dumping objects -> {149} normal block at 0x00365008, 4000 bytes long. Data: < > 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 Object dump complete. The program '[4984] Lab 03 - Iterator (nested).exe' has exited with code 0 (0x0).
- Refer to this link for resolving memory leaks.
Understanding Valgrind Output.(collapse)
- You can find helpful hints and explanations of Valgrind output here.
Railroad Criteria
Instructions for submitting your lab:
- Zip all your lab source files (.cpp, .hpp, .h) into one folder. DO NOT INCLUDE YOUR WHOLE PROJECT!
- Submit your zipped lab source using the "Labs" tab on the class website.
- Your submitted lab source will be compiled and executed using a lab gcc compiler.
- Both input and output files will be specified by command line arguments.
- An auto-grader will compare your output files with expected results and you will shortly receive an accumulative score.
- The auto-grade process may abort on the first error. If so, correct the error and try again.
- You may re-submit your zipped source as many times as you like.
Use the following input and resulting output files in testing the Railroad lab. Content should match exactly (excluding white-space) to receive full credit. Remember, purposely submitting inaccurate peer reviews is dishonest.
Input File | Output File | |
Test #1 | lab06_in_01.txt | lab06_out_01.txt |
Test #2 | lab06_in_02.txt | lab06_out_02.txt |
Test #3 | lab06_in_03.txt | lab06_out_03.txt |
Test #4 | lab06_in_04.txt | lab06_out_04.txt |
Test #5 | lab06_in_05.txt | lab06_out_05.txt |
The auto-grader will be expecting to find in your output file the following sections:
Fail |
Pass |
||
---|---|---|---|
The lab source will be peer reviewed using the following rubric:
No |
Partial |
Yes
|
||
---|---|---|---|---|
Overall Rating |
Easy to follow, readable, organized, and
well commented. |
|||
***NOTE: Any attempt to circumvent any lab requirement (ie, hard coding output, using loops instead of iterators where required, modifying a class interface, etc.) will be reported by peer reviewers and result in a zero score for the lab. |