Saint Louis University |
Computer Science 2100
|
Dept. of Math & Computer Science |
Please see the general programming webpage for details about the programming environment for this course, guidelines for programming style, and details on electronic submission of assignments.
The files you may need for this assignment can be downloaded here.
For this assignment, you must work individually in regard to the design and implementation of your project.
Please make sure you adhere to the policies on academic integrity in this regard.
Some common uses for stacks in software is to support moving back through the history of a web browser, or undoing editing steps in a word processor. Theoretically, such software could support arbitrary levels of such history, if a stack were to have unbounded capacity, but in pratice, there may be a fixed limit on the size of such a stack.
The issue arises as to what should happen when the capacity is exhausted and a new item is pushed onto the stack. One possibility is to throw an exception. But this is not how modern software behaves. For example, if a web browser were only willing to save 50 pages in its history, and you visit more pages, it will make room in the history for a new page by throwing away the least recently visited page (i.e., the bottom of the stack). The formal Stack interface does not provide any means for accessing or removing the object on the bottom of the stack.
In this assignment, we define a new ADT that we call a LeakyStack. The interface for a LeakyStack is very similar to the interface given for a Stack. However in the case when the capacity is exhausted, a call to push will successfully accept the new element, but at the expense of the loss of the least recently added element. We formalize this interface as an abstract class definition, provided online in file LeakyStack.h.
/** * Interface for a leaky stack: A collection of objects that are inserted * and removed according to the last-in first-out principle, but with * overflow handled by the removal of the least-recently accessed item. */ class LeakyStack { public: /** * Return the number of objects in the stack. * \return number of elements */ int size() const; /** * Determine if the stack is currently empty. * \return true if empty, false otherwise. */ bool empty() const; /** * Return a const reference to the top object in the stack. * \return const reference to top element * \throw runtime_error if the stack is empty */ const string& top() const; /** * Insert an object at the top of the stack. If the stack * is already at capacity, the oldest element will be lost. * \param the new element */ void push(const string& e); /** * Remove the top object from the stack. * \throw runtime_error if the stack is empty. */ void pop(); };
For this assignment, you will be required to give two different implementations for the LeakyStack interface, as described below. We will provide you with initial files that stub these classes.
One way to implement a LeakyStack is as follows. Use the ArrayStack implementation essentially verbatim from lecture, modifying only the push method accordingly. In the case when pushing onto a "full" stack, use a loop to shift all items one location earlier in the array. By doing this, you will lose your bottommost item while opening up the topmost location for the newly pushed object.
A second way to implement a LeakyStack is to use an array viewed in a circular manner (in a style similar to our ArrayQueue implementation). You can mark the "top" of the circular stack with an extra integer variable that is an index into your array. By also keeping explicit count of the current size of the stack, you can effectively identify the "bottom" of the stack as well, when needed. With a bit of care, you can more efficiently handle pushes, even those involving overflow.
Once you have completed your implementations, you are to perform some timing experiments described below. Also, you are to develop some unit tests for the project, also described below.
To make things a bit easier for this assignment,
we have intentionally defined the LeakyStack to be a stack of strings rather than using a generic item type. This means that you do not need to write templated code for this assignment.
our driver will not rely upon the existence of proper "housekeeping" methods (a copy constructor, assignment operator, and destructor). So you do not need to implement them, even though they would be necessary for legitimate versions of these classes.
All such files can be downloaded here. You should only modify the first four of those.
LeakyStackA.h
This file should be used to formally define the
LeakyStackA class, which is the first of the two
implementations of the LeakyStack interface.
The initial file that we provide is not complete. Rather it
defines all of the public methods that you will need to
implement, yet with stubs for most of the method bodies. You
will need to modify this file appropriately.
LeakyStackA.cpp
This file should contain the implementations for most of the
methods defined in the LeakyStackA class.
The initial file that we provide is not complete. Rather it
defines so-called stubs for each such method. The
stubs are syntactically correct so that they can be compiled,
but they are in no way semantically correct implementations of
the desired behaviors. You
will need to modify this file appropriately.
LeakyStackB.h
Analog to LeakyStackA.h but to be used for the
second of the two implementations as outlined in this
assignment.
LeakyStackB.cpp
Analog to LeakyStackA.cpp but to be used for the
second of the two implementations as outlined in this
assignment.
LeakyStack.h
This file formally defines the abstract class LeakyStack.
Driver.cpp
InputWrapper.h
These files provide a main driver to be used in testing your
program. The use of the driver is discussed in the next section.
TestEfficiency.cpp
This serves as another driver, but rather than allowing for
user interactive with a stack, it performs an efficiency test by
timing the execution of 5N pushes and N pops for a stack of
capacity N. Discussion of the timing experiments are
in a later section.
makefile
This makefile should allow you to rebuild your project by
simply typing 'make' rather than invoking the compiler
directly.
Driver is a text-based, menu-driven program for testing your LeakyStack implementations. The menu allows you to call any combination of LeakyStack methods, including reconstructing new instances of either variant with a desired capacity.
By default, the program takes input from the keyboard. However the driver has an optional feature which allows it to read input from a file rather than from a keyboard. The file should have the identical characters which you would use if you were typing them yourself on the keyboard. This feature simply allows you to test and retest your program on a particular input without having to retype the input each time you want to run the program. It is also convenient at times when running with a debugger to have the input read from a file rather than from the keyboard. If the program reaches the end of the file without exiting, then it will revert back to reading input from the keyboard for the remainder of the program.
To use this feature, you must specify the exact filename (including any suffix), as a single argument at runtime, using a syntax such as
./Driver inputfile
Part of developing good software is also knowing how to perform meaningful testing of your software. For example, suppose this class was used as part of a complete web browser. One approach to testing would be to wait until the entire browser project were completed and then to test the final result. But if something failed with the browser history, we would have to wonder whether the problem was due to an errant implementation of the data structure, to an errant use of a well-written data structure, or some other problem. Rather than relying on a test of the entire end product, a useful approach is to make sure that each individual piece works in isolation. This is called unit testing. For this assignment, you will develop a test plan for evaluating the correctness of the LeakyStack implementations without actually considering the larger application (in fact, we will not actually be writing any such hypothetical web browser). To perform unit testing of this data structure, you will use the provided driver which allows you to individually call any of the public methods supported by the data structure.
There is another issue we wish for you to consider when developing test plans. Often, testing is performed in close coordination with an examination of source code. For example, we may write some code or view some code, and then try to test that particular chunk of code. There is an inherent danger in relying only on such tests. In particular, if the tests are based on the same flow of thoughts as (errant) code, we are more likely to forget to test for a particular combination of events that were not considered by the code. The practice of "black box" testing is to design a set of tests based purely on the formal specifications for a data structure, but without actually seeing the source code.
On this assignment, we will have you develop a black-box unit test plan for LeakyStack implementations. Specifically, you must submit a plain text file named "inputfile" which will be used to test other students' implementations. The goal is to create test input which causes as many as possible of the other students' programs to fail in some way. Because our execution of student tests will be automated, you must make sure that your "inputfile" strictly adheres to the file format which is expected by the text-based driver, Driver, described earlier. Your file may use at most 100 commands. If your input file contains more than the prescribed number of commands, we will simply truncate the end of the file. In order to make sure that your inputfile follows the proper format, we strongly recommend that you run your program on your inputfile to make sure it is accepted by the driver.
Here is a sample input file, appropriately formatted.
TestEfficiency is a second program that will be used to test the efficiency of your two implementations. It does not allow for user interaction. Instead, it performs an experiment in which it repeatedly pushes elements onto a leaky stack, well beyond the stated capacity. Specifically, you may specify a value N that is the capacity of the stack, and then it measures the amount of time that it takes to perform 5N pushes followed by N pops (although it reports its results in terms of the average time per operation).
This program should be invoked using a syntax such as
./TestEfficiency B 10000to test the efficiency of LeakyStackB with capacity 10000.
Please test both versions of your completed leaky stacks on a reasonable sample of values for N, and record the results. We recommend that you look at what happens each time N doubles, for example, perhaps testing 1000, 2000, 4000, 8000 (although you may need to adjust the scale depending upon the speed of the machine on which you test). Please make sure you pick big enough tests so as to avoid neglible running times.
Source Code
Please submit all source code files which you have either
modified or added. For this assignment, we expect this to include the
four files LeakyStackA.h,
LeakyStackA.cpp,
LeakyStackB.h, and
LeakyStackB.cpp.
Test Input
Please submit a single file, inputfile, as outlined in the
earlier section on black-box testing.
Readme File
For this assignment, please include:
The assignment is worth 20 points. Sixteen points will be awarded based on our own evaluation of your assignment and the readme file. Two additional points will be awarded fractionally based on how well your program performs on other students' test inputs. The final two points will be awarded fractionally based on how well your test input fools other students' flawed programs.
When dealing with an array in a circular fashion, there are many
possible ways for adjusting indicies as they "wrap around" the ends of
the array. The text chooses to use modular arithmetic (the "%"
operator in C++). Please note that its precedence is equivalent to
multiplication and division, and therefore the expression