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
Begin with understanding the chapters on
Lists
and
Deques
and a working knowledge of the techniques involved in creating linked structures
dynamically.
The official development/testing/assessment environment is specified in
the Course Organizer.
Make sure you understand the implementation plan for
List<T> described in the references above.
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.
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
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.
Create the other three deliverables:
- A header file file
list1.h defining the class
templates alt::List, alt::ConstListIterator,
and alt::ListIterator conforming to the specs below.
- A source code file list1.cpp implementing the class templates
defined in the header file.
- 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.
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.
Turn in the files name.com, list1.h, list1.cpp, and log.txt
by executing the submit script.
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
The first deliverable name.com is a command file for flist.cpp
[ElementType = char] such
that:
- The characters of your first name are inserted into x1 in alphabetical order,
with the first letter of your name capitalized
- The characters of your last name are inserted into x2 in alphabetical order,
with the first letter of your last name capitalized
- 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 '_'.
- Note character insert order is alphabetical, and list traversal order spells your
name.
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.)
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.)
Required Runtime Constraints.
- PushFront, PopFront, and PushBack must have constant runtime O(1).
- PopBack should have runtime Θ(n).
- 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.
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.
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.
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:
- 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.
- 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.
|