Project 8: Queue

A generic Queue data structure

Revision dated 11/05/18

Educational Objectives: After completing this assignment the student should have the following knowledge, ability, and skills:

Operational Objectives: Create the generic container class fsu::Queue<T> that satisfies the interface requirements given below, along with an appropriate test harness for the class.

Deliverables: Four files tqueue.h, fqueue.cpp, makefile, and log.txt.

Assessment Rubric

=================================================
Rubric to be used in Assessment           P7  P8
-------------------------------------------------
test builds:    [0..2 pts each]
  fstack.x [student harness]               x   
  fstack_char.x                            x   
  fstack_int.x                             x   
  fstack_String.x                          x   
  fstack_T.x                               x   
  fqueue.x [student harness]                   x
  fqueue_char.x                                x
  fqueue_int.x                                 x
  fqueue_String.x                              x
  fqueue_T.x                                   x
build in2post.x [0..10 pts]                x   x
tests:          [0..5 pts each]
  fstack_char.x                            x   
  fstack_int.x                             x   
  fstack_String.x                          x   
  fstack_T.x                               x   
  fqueue_char.x                                x
  fqueue_int.x                                 x
  fqueue_String.x                              x
  fqueue_T.x                                   x
in2post.x                  [0..10]         x   x
log.txt                   [-25..5] each:   x   x
code quality              [-25..5] each:   x   x
dated submission deduction [2 pts each]: ( 0)( x)
                                          --  --
total P7                        [0..50]:  xx      <-- cannot excede 40 without proj8 and in2post
total P8                        [0..50]:      xx    
=================================================

Note 1: Projects 7 and 8 will be assessed together for a total of 100 points for both projects. The above shows the weighting of the two parts of the coupled assignment. Project 7 alone can only achieve 40 points, because in2post requires interaction between stacks and queues.

Note 2: Code Quality and Log entries will be assessed for both Stack and Queue simultaneously. Be sure that your log.txt covers both projects in the same log.

Abstract Data Types

An abstract data type, abbreviated ADT, consists of three things:

  1. A set of elements of some type T
  2. Operations that may modify the set or return values associated with the set
  3. Rules of behavior, or axioms, that determine how the operations interact

The operations and axioms together should determine a unique character for the ADT, so that any two implementations should be essentially equivalent. (The word isomorphic is used to give precision to "essentially equivalent". We'll look at this in the next course.)

Queues

The queue ADT is used in many applications and has roots that pre-date the invention of high-level languages. Conceptually, queue is a set of data that can be expanded, contracted, and accessed using very specific operations. The queue ADT models the "FIFO", or first-in, first-out rule. The actual names for the queue operations may vary somewhat from one description to another, but the behavior of the abstract queue operations is well known and unambiguously understood throughout computer science. Queues are important in many aspects of computing, ranging from hardware design and I/O to inter-machine communication and algorithm control structures.

Typical uses of ADT Queue are (1) buffers, without which computer communication would be impossible, (2) control of algorithms such as breadth first search, and (3) simulation modelling of systems as diverse as manufacturing facilities, customer service, and computer operating systems.

Abstract Queue Interface

The queue abstraction has the following operations and behavior:

Application: Converting Infix to Postfix Notation

As one example of the use of ADTs in computing, consider the following function that illustrates an algorithm for converting arithmetic expressions from infix to postfix notation:

...
#include <tqueue.h>
#include <tstack.h>
...
typedef fsu::Queue < Token > TokenQueue;
typedef fsu::Stack < Token > TokenStack;
// a Token is either an operand, an operator, or a left or right parenthesis
...
bool i2p (TokenQueue & Q1, TokenQueue & Q2)
// converts infix expression in Q1 to postfix expression in Q2
// returns true on success, false if syntax error is encountered
{
   ...
   TokenStack S;              // algorithm control stack
   Q2.Clear();                // make sure ouput queue is empty
   while (!Q1.Empty())
   {
     // take tokens off Q1 and use them to build Q2
     // if syntax error is detected return false 
  }
   return true;
}  // end i2p()

This is a complex algorithm, but not beyond your capability to understand. The main points to displaying it here are: (1) to illustrate how stacks and queues may be used in implementing applications, and (2) to let you know that your code must be compatible with such apps. in2post.cpp is one of our tests!

Queue Implementation Plan

We will implement the queue abstraction as a C++ class template Queue with the following public methods:

// Queue < T > API
public:
  void     Push     (const T& t);
  T        Pop      ();
  T&       Front    ();
  const T& Front    () const;
  size_t   Size     () const;
  bool     Empty    () const;
  void     Clear    ();

  // extra goodies
  size_t   Capacity () const;
  void     Append   (const Queue& q);
  void     Release  ();

There should be a full complement of self-management features:

public:
  Queue             ();              // default constructor
  Queue             (char ofc);      // sets output formatting character
  Queue             (const Queue&);  // copy constructor
  ~Queue            ();              // destructor
  Queue& operator = (const Queue&);  // assignment operator

The display and its settings will be maintained as follows:

public:
  void     Display    (std::ostream& os) const; // output queue contents through os - depends on ofc
  void     SetOFC     (char ofc);         // sets output formatting character
  void     Dump       (std::ostream& os) const; // output all private data (for development use only)
  
private:
  char    ofc_;

(There is no need for a directional specificer - queues are always displayed front-to-back.)

Unlike Stack, Queue requires access to data at both the front and back, which makes an array implementation impractical. We will use a linked list implementation using a link class defined as follows:

class Link
{
  Link ( const T& t );  // 1-parameter Link constructor
  T      element_;
  Link * nextLink_;
  friend class Queue<T>;
};

Note that all members of class Link are private, which means a Link object can be created or accessed only inside an implementation of its friend class Queue<T>. The only method for class Link is its constructor, whose implementation should just initialize the two variables. (This can be done inside the class definition, as shown below.)

The private Queue variables for this implementation will be exactly two pointers to links, the first and last links created. Including the definition of the helper class Link, the private section of the class definition should be like the following (with implementor-chosen private variable names):

template < typename T >
class Queue
{
...
private:
  class Link
  {
    Link ( const T& t ) : element_(t), nextLink_(nullptr) {}
    T      element_;
    Link * nextLink_;
    friend class Queue<T>;
  };
  Link * firstLink_;
  Link * lastLink_;
};

The class constructor will have responsibility for initializing the two variables to nullptr. These two pointers will be zero exactly when there is no data in the queue (the queue is empty). Links for data will be allocated as needed by Push() and de-allocated by Pop(). These operations will also need to re-direct appropriate link pointers in the dynamically allocated links, and maintain the class variables firstLink_ and lastLink_ correctly pointing to the links containing the first and last elements of the queue. The destructor should de-allocate all remaining dynamically allocated links in the queue. The Size() method will have to loop through the links to count them, whereas the Empty() method can do a simple check for emptiness of the queue.

Because this implementation relies on dynamically allocated memory, the container makes no restrictions on the client program as to anticipated maximum size of the queue.

Procedural Requirements

  1. Continue your log.txt from project 7 (Stack). This log should serve for both projects and will be assessed when Stack and Queue are assessed together.

  2. Create and work within a separate subdirectory cop3330/proj8. Review the COP 3330 rules found in Introduction/Work Rules.

  3. After starting your log, copy the following files from the course directory [LIB] into your proj8 directory:

    proj8/in2post.cpp
    proj8/constTest.cpp
    proj8/deliverables.sh
    area51/in2post*.x
    area51/fqueue*.x
    

    The naming of these files uses the convention that _s are compiled for Sun/Solaris and _i are compiled for Intel/Linux. Use one of the sample client executables to experiment to get an understanding of how your program should behave.

  4. Define and implement the class template fsu::Queue<T> in the file tqueue.h. Be sure to make log entries for your work sessions.

  5. Devise a test client for Queue<T> that exercises the Queue interface for at least one native type and one user-defined type T. Put this test client in the file fqueue.cpp. Be sure to make log entries for your work sessions.

  6. Test Stack and Queue using the supplied application in2post.cpp. Again, make sure behavior is appropriate and make corrections if necessary. Log your activities.

  7. General Identical Behavior Requirement. Except where specifically noted otherwise, behavior of executables must be identical to those of the area51 benchmark executables.

  8. Turn in tqueue.h, fqueue.cpp, and log.txt using the submit.sh submit script.

    Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.

Code Requirements and Specifications

  1. Your makefile should build the target fqueue.x. If you have other targets to build, that is not a problem as long as this target is there.

  2. Queue should be a proper type, with full copy support. That is, it should have a public default constructor, destructor, copy constructor, and assignment operator. Be sure that you test the copy constructor and assignment operator.

  3. The Queue constructor should create an empty queue with no dynamic memory allocation.

  4. The Queue<T>::Push(t) operation must dynamically allocate memory required for storing t in the queue. Similarly, the Queue<T>::Pop() operation must de-allocate memory used to store the element being removed from the queue.

  5. As always, the class destructors should de-allocate all dynamic memory still owned by the object. The stack and queue implementations will be very different.

  6. The static method NewLink is intended to be a customizable "wrapper" for operator new, with implementation as follows:

    template < typename T >
    typename Queue<T>::Link* Queue<T>::NewLink (const T& t)
    {
      Link * newLink = new(std::nothrow) Link (t);
      if (nullptr == newLink)
      {
        // exception handler
        std::cerr << "** Queue error: memory allocation failure\n";
        return nullptr;
      }
      return newLink;
    }
    

    Using NewLink(t) instead of "new Link(t)" effectively turns off the standard exception handler (via the "nothrow" argument) and provides a place for exceptions to be handled internally by the container.

    Use this method to replace all calls to operator new.

  7. Use the implementation plan discussed above. No methods or variables should be added to these class, beyond those specified above and in the implementation plan.

  8. The Display(os) method is intended to output the contents through the std::ostream object os in front-to-back order. The second parameter ofc is a single output formatting character that has the default value '\0'. (The other three popular choices for ofc are ' ', '\t' and '\n'.) The implementation of Display must recognize two cases:

    1. ofc_ == '\0': send the contents to output with nothing between them
    2. ofc_ != '\0': send the contents to output with ofc_ preceding each element of the queue

    Thus, for example, q.Display(std::cout) would send the contents of q to standard output.

    NOTE: '\0' is NOT nothing!

  9. The output operator should be overloaded as follows:

    template < typename T >
    std::ostream& operator << (std::ostream& os, const Queue<T>& q)
    {
      q.Display (os);
      return os;
    }
    

    The overload of operator <<() should be placed in your queue header file immediately following the class definition.

  10. The class Queue should be in the fsu namespace.

  11. Extras. Here are descriptions of the remaining "extra goodies":

    1. Append(q) is intended to capture the process of pushing all elements of the input q, without changing q. Append should then be called by implementations of both the copy constructor and the assignmenet operator.
    2. Release() is intended to make the qwueue empty. It should be called by Clear as well as the destructor.
    3. Dump(os) can be implemented as a repeat of Display: { Display(os); }
    4. Capacity() for this implementation is just an alias for Size: { return Size(); }
  12. The file tqueue.h should be protected against multiple reads using the #ifndef ... #define ... #endif mechanism.

  13. The test client program fqueue.cpp should adequately test the functionality of queue, including the output operator. It is your responsibility to create this test program and to use it for actual testing of your queue data structure.

Hints