Put It Together
For this assignment, you are to create two template classes,
rpt_queue and its inverse expand_queue.
The rpt_queue class behaves somewhat like an ordinary
queue, except that it groups matching items together. It allows
you to push single items like a normal queue, but when you pop you
get an item and a repeat count. When you push the same item three
times in a row, pop gives you the item once with a repeat count of three.
The expand_queue is the inverse. Its push takes an item and
a count, and its pop will deliver the item the indicated number of times
The Repeating Queue
The
rpt_queue template class takes a single
typename
template parameter giving the type of object contained in the queue.
An object
q of type
rpt_queue has the following interface:
q.push(const T &item)
Push a copy of item onto the queue.
q.pop(T &item)
Pop items from the front
of the queue. If no item is available, the method returns
zero and does nothing else. If a run is available at the the front
of the queue, pop stores
one item in the parameter item,
removes the run from the queue, and returns the size of the run.
q.has_item()
Returns true if pop will succeed (return a positive value), or
false otherwise.
q.empty()
Returns true if q is empty, false otherwise.
q.separate()
Specifies that the next item pushed, if any, should be considered
different from the last item pushed, if any. That is, the next push
will always start a new group of matching items, whether or not
it actually equals the previous item.
Note: It is possible for has_item and empty to both be
false. This happens because a pushed item does not become available until
its count is known, which does not happen until either a different item
is pushed, or separate is called. The following examples will
hopefully make this clearer.
For example:
#include <iostream>
using namespace std;
#include "rtpq.h"
int main()
{
rpt_queue<int> queue;
for(int i: { 3, 4, 4, 3, 3, 7, 7, 7, 10 }) queue.push(i);
while(queue.has_item()) {
int item;
int count = queue.pop(item);
cout << "[" << item << "(" << count << ")] ";
}
cout << "Queue is " << (queue.empty() ? "" : "not ") << "empty."
<< endl;
}
This program has the output
[3(1)] [4(2)] [3(2)] [7(3)] Queue is not empty.
You can see that the program pushes the listed items, then
pops all available groups from the stack. Each run of repeated input
items is returned by a single pop, with the appropriate repeat count.
But, the queue does not return the 10 that was pushed last.
It can't, because it doesn't know what you plan to push next. You might
push another 10, so it doesn't know the correct count. Therefore, even
though the queue is not empty, there is no item available.
To get everything from the queue, you can make this change:
#include <iostream>
using namespace std;
#include "rtpq.h"
int main()
{
rpt_queue<int> queue;
for(int i: { 3, 4, 4, 3, 3, 7, 7, 7, 10 }) queue.push(i);
queue.separate();
while(queue.has_item()) {
int item;
int count = queue.pop(item);
cout << "[" << item << "(" << count << ")] ";
}
cout << "Queue is " << (queue.empty() ? "" : "not ") << "empty."
<< endl;
}
Which has has the output
[3(1)] [4(2)] [3(2)] [7(3)] [10(1)] Queue is empty.
By running
separate after pushing the 10, the queue knows that
the group has ended, and need not worry about additional items pushed.
The Expanding Queue
The
expand_queue is perhaps more straightforward.
The
expand_queue template class takes a single
typename
template parameter giving the type of object contained in the queue.
An object
q of type
expand_queue has the following interface:
q.push(int count, const T &item)
For positive count,
this pushes count repetitions of value item.
For zero or negative count, nothing is pushed.
q.pop(T &item)
Pop an item from the stack. If no item is available, the method returns
false and does nothing else. If some item is available, pop stores
a copy in the parameter item removing it from
q, and returns true.
Item will be returned for
count successive pop calls, where count is the parameter
used when item was pushed.
q.has_item()
Returns true if pop will succeed (return a positive value), or
false otherwise.
q.empty()
Returns true if q is empty, false otherwise.
Note that, unlike the first class,
has_item() is true exactly when
empty() is true.
The program
#include <iostream>
#include <string>
using namespace std;
#include "rtpq.h"
int main()
{
expand_queue<string> queue;
queue.push(1, "bill"); queue.push(3, "frank"); queue.push(0, "donald");
queue.push(1, "evan"); queue.push(2, "evan"); queue.push(2, "alice");
string item;
while(queue.pop(item))
cout << item << " ";
cout << endl;
}
has the output
bill frank frank frank evan evan evan alice alice
Arrangement
Both of the classes are templates, so they should be placed entirely in
a .h file; don't create a .cpp file. Put both in the
same .h file, which you may name as you like. The examples given
here assume the name rtpq.h, but this is not a requirement.
Do use the #ifndef/#define/#endif pattern
as described earlier in your .h file to avoid multiple declaration
errors at compile time.
Notes
For rpt_queue,
I used a C++ standard list as a private data field
to keep the contents of the queue. (Other
structures might be used, but a vector is probably not the best choice
since you only add and remove at one end.) You will also need to
have a count for each item. You can count them going in or going out.
If you do it going out, you can just keep all the items in a list
(or other structure), so repeat objects are simply repeated in your list.
Then pop must detect the duplication, remove the entire run, and return
a single item with its count. While this can be made to work, I think
you may have serious difficulty keeping track of separations, and
detecting
when no item is available, even though the list is not empty.
The other way is to count them going in, using
an internal data structure that
contains each item along with its count. You might use two parallel lists,
one of items and another list of counts, or
make a simple class to contain an item and count, and construct a list
of those. This will make it easier to implement separate(), since
you just create a new group afterwards.
Instead of creating a small class to hold an item and a count,
you could use the
pair
utility from the C++ library. But don't bother with that if
you're struggling with this already.
Your object will need to keep all available items; that's what the
list is for. There also may be an unavailable item: the last one which
has been pushed but not separated. I kept mine in the list also,
simply being the last item there. A separate (private) boolean field
remembers if that last item has been separated or not.
An alternative would be to create
separate (private) data field(s) to hold any unavailable item and its
count, which you add to the list when it is separated (by a different
push or call to separate). This way, the list holds exactly all
the available items. If there is no unavailable item present, the
unavailable item's count
is zero.
The expand_queue class is similar but simpler. You can
give push a loop that simply adds the item to the internal
list the needed number of times, then the rest is
very similar to a standard queue. Alternatively, you can
keep both push parameters in the internal list and let pop
obey the count and return the item count times before removing it
from the list. The first approach is probably easier.
Submission
When your program works, is well-commented, and nicely indented,
submit over the web
here.