CSC 161 Grinnell College Fall, 2013
Imperative Problem Solving and Data Structures


This laboratory exercise introduces the concept of the queue abstract data type and provides experience with both the array and the linked-list implementation of this ADT.

Queues as ADTs

The reading describes a queue as an ADT that can store data and that has the following operations:

Implementing Queues with Arrays

In this part of the lab, we will focus on arrays of strings, and this leads to the following declarations for queues:

#define MaxQueue 50  /* size of all queue arrays */

typedef struct {
   int first;
   int last;
   int count;
   char * queueArray [MaxQueue];
} stringQueue;

With this framework, the signatures for the various queue operations might be as follows:

   void initializeQueue (stringQueue * queue)
   int empty (stringQueue queue)
   int full (stringQueue queue)
   int enqueue (stringQueue * queue, char* item) 
      (returns length of string added or -1 if queue is full)
   char * dequeue (stringQueue * queue)
      (returns string removed from queue)

Additional implementation notes may be found in today's reading on Queues.

  1. Write an implementation of a queue of strings by implementing these operations.

  2. Use the queue operations within a main procedure to provide thorough testing of each operation.

  3. Add to the queue ADT an additional procedure print that displays each of the elements of the queue on a separate line (without actually removing any of them from the queue).

  4. An alternative approach for the dequeue procedure would add a parameter item to the list of parameters and change the return type to an int type. The idea is that the string would be returned as a parameter char * item and the procedure would return the length of the string item or -1 if the queue was empty. The relevant procedure signature might be:

       int dequeue (stringQueue * queue, char ** item)

    and this procedure would be called within the context:

      char * frontItem;
      int returnValue;
      stringQueue myQueue;
      returnValue = dequeue (&myQueue, &frontItem)
    1. The parameter item has type char **, and the call to queue includes &frontItem. Explain why ** and & are needed here.
    2. Write this alternative version of the dequeue operation.

Implementing Queues with Linked Lists

  1. Rewrite your program from the first part of this lab on Implementing Queues with Arrays, changing the implementation to use a linked list instead of an array.

  2. The enqueue operation allocates space for a new node and copies the string item into that node. The dequeue could return a pointer to the character array within the node or it could copy the string back to a newly created array before passing back a reference. Is there an advantage of one of these approaches over the other? Explain.

    Note: dequeue should deallocate space for the node that is removed.

  3. Write a print function that prints all elements on a queue, from the head of the queue to its tail. (This function can be helpful in testing.)

For those with extra time:

  1. Test your program carefully.

    1. Think of a set of test cases that will thoroughly test your program. What test cases should you include?

      It is troublesome, but true, that there is as much art as science in testing programs well, given that one goal of testing is to think of unusual occurrences that may not come readily to mind.

      At the very least, be sure that your cases include an example of each response required by the problem specification. (For example, you should consider when error conditions should arise, and your testing should include those cases — does the program handle these cases appropriately?) You should also pay particular attention to "boundary cases" that may arise: in the context of queues, reasonable candidates for boundary cases might include adding or deleting items from queues with 0, 1, or perhaps more items.

    2. Note that this program for queues is written to accept input repeatedly from the user. To "automate" such a user, enter your test data in a file with one input value per line, so that the newline character in the file simulates the user pressing the enter key. You do not need to type ctrl-d into your test data file to indicate the end of the file: just end the file, and your program will correctly detect when it reaches the end of the file.

      Now rebuild your program and run it, re-directing it to get its input from the test data file. For example, your run command might look like this:

      ./a.out < queue-test.dat

      Obviously, you will want to examine your output for correctness.

      Your output from running the program this way may look strange because the input prompts appear, but the user input does not. Depending on the situation, you may want to comment the prompts out of your code, or you may want to just put up with odd looking output.

      The value of testing C programs in this way is that it allows you to use the same test cases multiple times without retyping them. Why is this useful? Consider the possibility that the first time you test a given case, your program gives an incorrect response. Once you fix the problem, you will want to test it again, and you will want to be sure that you have tested it on the same data. Further, you will want to re-test all of your previously working cases to make sure that your most recent change did not cause other cases to fail.

      It is good practice (though a somewhat difficult habit to get started) to maintain a set of test cases for each program you write. This makes it easy to re-test your entire program when a new change is made. Re-running all your test cases for each new change is known as system testing.

Feedback Welcome

Development of laboratory exercises is an interative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.