Project 1: Singly Linked List

Revision dated 1/14/16

Educational Objectives: After completing this assignment, the student should be able to accomplish the following:

  • Use linked structures to implement a dynamically sized data structure
  • Test dynamic data structures for functionality
  • Test dynamic data structures for resource leaks
  • Implement the ADT List using linked structures
  • Explain the use of single and double links and the advantages of each in an implementation
  • Define and implement iterators for a data structure

Operational Objectives: Supply source code implementing the template class alt::List<T> and its associated Iterator classes. Code should be thoroughly tested for functional correctness, robustness, and memory management. The supplied code should function correctly and be free of memory leaks, and your tests should provide evidence of both. This evidence should be summarized in a test report.

Deliverables: Four files: name.com, list1.h, list1.cpp, log.txt

Variations on List implementations

These are some of the commonly encountred variations in the way the List data structure can be implemented:

  • Doubly linked: Each node has two links, pointing to the next and previous links in the structure. Double links facilitate navigation both "forward" (toward the back) and "backward" (toward the front) in the structure, and List::Iterators can therefore be made bidirectional, with both operator++() and operator--() in the Iterator API. fsu::List<T> has this feature.

  • Singly Linked: Each node has only one link, pointing to the next link in the structure. Single links facilitate only forward navigation, and List::Iterators are forward only, with operator++() but not operator--() in the Iterator API. Singly linked structures are significantly more memory efficient, but some List operations are less time efficient.

  • Head and Tail Nodes: Some implementations create extra nodes that are a hidden (private) part of the structure and are positioned at one or both ends: before and/or after all of the links containing client data.

    Head and tail nodes can be useful in two ways: (1) simplifying the implementation code by eliminating special cases and (2) providing places for iterators to end up after a traversal. In the case with no tail node, for example, an end iterator is essentially a nullptr, from which we cannot recover using the Iterator API. fsu::List<T> is implemented with both head and tail nodes.

  • Circularly Linked: The cases above are all examples of "linear" linked structures, that is, with a fixed notion of front [first link] and back [last link]. fsu::List<T> is implemented in this linear fashion. An alternative design is the circular construction, in which no link pointer field is null. The resulting linked structure is called the "carrier ring". Wherever one starts in the carrier ring, the nodes can be traversed using the Link::next pointer all the way around the ring back to where the traversal began. (If the links are double, the same would hold for reverse traversals.) Circularly linked structures can be singly or doubly linked with support for forward or bidirectional iterators as the case may be.

    One major advantage of using a circular list design is that links that are removed from the visible list can be saved in the carrier ring and re-used later when needed, thus saving significant processing time of calls to operators new and delete. This is facilitated by maintaining class variables pointing to the first and one-past-the-last links in the carrier ring, indicating where the current public list is stored in the ring. One node in the ring is always excluded from the list in order to disambiguate the empty list from the full carrier, but the excluded node varies dynamically as the list is used.

    A disadvantage of using the circular structure is caused by the necessary dynamic location of "first" and "last" in the ring: it is not practical to use head and tail nodes, so design-specific ways to detect end of iteration must be created and maintained.

Procedural Requirements

  1. Begin with understanding the chapters on Lists and Deques and a working knowledge of the techniques involved in creating linked structures dynamically.

  2. The official development/testing/assessment environment is specified in the Course Organizer.

  3. Make sure you understand the implementation plan for List<T> described in the references above.

  4. Work within your subdirectory called cop4530/proj1. Keep in mind that with all assignments it is a violation of course policy and the FSU Honor Code to give or receive help on assignments from anyone other than the course instruction staff or to copy code from any source other than those explicitly distributed in the course library.

  5. Copy the following files from the course LIB into your proj1 directory:

    LIB/tests/flist1.cpp
    LIB/tests/mlist1.cpp
    LIB/proj1/list1.api
    LIB/proj1/makefile
    LIB/proj1/deliverables.sh
    LIB/scripts/submit.sh
    
  6. Create the first deliverable: a text file name.com that is a command file for flist.cpp [ElementType = char] that inserts the characters of your full name in alphbetical order such that your name appears correctly spelled in the list. This exercise will help understand the List API and also how to devise tests for List using the supplied test harness.

  7. Create the other three deliverables:

    1. A header file file list1.h defining the class templates alt::List, alt::ConstListIterator, and alt::ListIterator conforming to the specs below.
    2. A source code file list1.cpp implementing the class templates defined in the header file.
    3. A text file log.txt consisting of a log of all development activity, including documentation for all testing. All four files should be placed in the proj1 directory: These are the project deliverables.

  8. Also in the log, keep detailed notes on procedures and results as you test your implementation list1.cpp. Use these testing notes to create a summary testing report to conclude the file log.txt.

  9. Turn in the files name.com, list1.h, list1.cpp, and log.txt by executing the submit script.

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

Technical Requirements and Specifications

  1. The first deliverable name.com is a command file for flist.cpp [ElementType = char] such that:

    1. The characters of your first name are inserted into x1 in alphabetical order, with the first letter of your name capitalized
    2. The characters of your last name are inserted into x2 in alphabetical order, with the first letter of your last name capitalized
    3. Finishing with the commands to accomplish x3 = x1; x3 += x2; x3.Display(std::cout) results in printing your name to screen, with first and last name separated by the underscore character '_'.
    4. Note character insert order is alphabetical, and list traversal order spells your name.
  2. Your implementation of alt::List should follow the packaging methodology used for fsu::List: a header file list1.h and a "slave" file list1.cpp. The header file contains the class API (including Iterator classes). The code file contains implementing code. The code file is #included into the header file after all of the API is defined but before the multiple read protection and the namespace are closed. (See the chapter on Vectors for an explanation of "slave" file.)

  3. Your implementation should follow the singly linked list plan. You may choose to use head and/or tail nodes at your discretion. (However, pay attention to the runtime requirements below.)

  4. Required Runtime Constraints.

    1. PushFront, PopFront, and PushBack must have constant runtime O(1).
    2. PopBack should have runtime Θ(n).
    3. Insert(i,t) should have runtime proportional to the position of i in the list.

    Note that 3.a. means that we can use alt::List to efficiently implement both Stack and Queue: Stack using PushFront/PopFront and Queue using PushBack/PopFront.

  5. Much of the implementation can be derived from the doubly-linked version named fsu::List that is available in your library as LIB/tcpp/list.h with slave file LIB/tcpp/list.cpp. There will be exceptions however where you will need to innovate.

  6. Your implementations should be tested for both functionality and memory containment using at least the classes T = char and T = fsu::String. Two test programs, clients of alt::List<T>, are supplied. Specific instructions for testing for memory leaks are included as comment at the top of the file mlist1.cpp. DO NOT TEST FOR MEMORY LEAKS WITHOUT FOLLOWING THESE INSTRUCTIONS.

  7. Document all testing in your log.txt, which will be collected by the submit script. This file should contain your daily activity log as well as document your testing - what was done, what the results were, and how those results helped make the software correct.

Hints:

  • The following files are used directly from the course library:

    tcpp/list.h         // defines fsu::List<T> and list iterator classes
    cpp/xran.h          // the fsu::xran family of random object generators
    cpp/xran.cpp        // ... these are used by mlist.cpp
    cpp/xranxstr.h
    cpp/xranxstr.cpp
    

  • The deliverable name.com for Chris Lacher is shown here:

    #
    # name.com
    # 
    # input order:     Chirs acehLr
    # traversal order: Chris Lacher
    #
    
    11C
    12h
    12i
    1a
    1++
    1++
    1ir
    12s
    12_
    21a
    22c
    22e
    2a
    2++
    2++
    2ih
    21L
    22r
    3=1
    3+=2
    3d
    q
    

    You can copy/paste this set of commands into a file and run it as follows:

    flist.x name.com
    

    and see the result "Chris_Lacher" to screen at the last command. You can copy flist.x from area51/flist_i.x, or you can compile your own using the makefile.

  • The file flist1.cpp contains a typical "functionality" test program. The idea is to provide access to the entire public interface of class alt::List<> and alt::ListIterator<> so that you can perform operations on three distinct List objects and associated iterators. It is up to you to use this test program effectively.

  • The file mlist1.cpp contains a dynamic test for correct memory and pointer management in the implementation of alt::List<> and alt::ListIterator<>. In contrast to the functionality test, this one runs without user input. It sets up three List<> objects and associated iterators, much as is done in flist, but the operations are called randomly in a loop that runs until Ctrl-C is entered or until the program crashes. This is a very dangerous program that will crash the entire server on which it runs if a defective implementation of List is tested without careful containment of the runspace for the program. Therefore it is imperative that the precautions delineated in the documentation be followed.

  • A makefile for your project that compiles separate executables for the client programs is supplied. You can compile the two supplied test programs using this makefile by entering the command "make all". You can compile individual test programs or any other target in the makefile by entering "make xxx" where "xxx" is the target. For example, "make flist.x" creates the executable for flist.cpp and "make mlist.o" creates the object code for mlist.cpp.

  • There are two good strategies to get started on the code for the project. Both approaches begin by copying list1.api to list1.h and make any additions or modifications that you know will be needed in list1.h. At a minimum you will need to correct file header documentation. Then follow one of these options:

    1. Copy LIB/tcpp/list.cpp to your list1.cpp and proceed to take out all of the code that cannot work because it refers to "prev_" pointers.
    2. Start a new file list1.cpp and implement the prototypes in list1.h one at a time, using the List lecture notes or the code file LIB/tcpp/list.cpp as much as possible for hints and direction.

    The amount of work required is about the same for either option. Option 2 may be simpler, and you will probably learn more following that path. But either way works.

    CAUTION: we will not want to see ANY evidence left over from LIB/tcpp/list.cpp or LIB/tcpp/list.h in your code.

  • Sample executables for flist, flist1, mlist, and mlist1 are available in LIB/area51.