CSc 220 Assignment 5

Put It Together

Assigned
Due

Nov 12
85 pts
Dec 10

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.