Project 1: Doubly Linked Circular List
Circular list implementation with memory conservation and dynamic head and tail nodes
Revision dated 01/05/19
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
=========================================================
Rubric to be used in assessment
---------------------------------------------------------
name.com [0..5]: x
build (student makefile) [0..5]: x
build (assess makefile) [0..5]: x
testing:
ftest.x com1 [0..5]: x
ftest.x com2 [0..5]: x
ftest.x com3 [0..5]: x
ftest.x com4 [0..5]: x
mtest.x [0..5]: x
log & testing report [0..5]: x
requirements and specs [0..5]: x
software engineering [-10..0]: ( x)
dated submissions [-2 pts each]: ( x)
--
total: [0..50]: xx
=========================================================
Operational Objectives:
Supply source code implementing the template
class alt2c::List<T> and its associated Iterator classes.
This is a List implementation using a circular linked list model with floating
head and tail nodes and memory conservation.
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:
Five files:
name.com,
list2c.h,
list2c.cpp,
makefile2c,
log.txt
Variations on List implementations
These are some of the commonly encountered 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.
Linearly Linked: The typical use of linked nodes to
implement List has some nodes with null link pointers. At the end of
the list, there is a last node whose next pointer is null. In the double
linked model, there is also a first node whose prev pointer is null. These
two nodes define the begining and end of the list data, and a list can be
traversed by starting at the beginning node and following the next pointers
until a null is encountered.
alt1::List<T> is implemented as a singly linked linear
structure without head and tail nodes.
alt2::List<T> is implemented as a doubly linked linear
structure without head and tail nodes.
fsu::List<T> is implemented as a doubly linked linear
structure with 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.
Head and tail nodes can be used in the circular model as well, providing a "safe
harbor" for iterators to exist before the first and after the last nodes
containing list values. In this implementation both the head and tail nodes are
outside the list data, essentially demarking a linear list inside the
circular carrier ring. The head and tail nodes are always extra nodes in the
ring, not containing data belonging to the list. Exactly which two nodes
are designated "head" and "tail" may vary
dynamically as the list is used (the "floating" case) or remain static (the
"fixed" case). Any extra nodes stored in the ring are in the
"dark side" between the tail node and the head node.
In the floating case, due to the dynamic nature of the designated head and tail nodes in a circular
list implementation, clients must
always assume iterators are invalidated by any modification of the list.
Additional care must be taken to handle iterators correctly. Iterators can
inadvertantly point into the "dark side" of the carrier ring. For example, an
End iterator can be incremented past the end of the list. As another example, the loop
for(i = list.Begin(); i.Valid(); ++i) {}
runs forever!
In the fixed case, the End() iterator has a stable meaning, but iterators
pointing into the body of a list may still be rendered invalid by changes made
to the list.
Developers and users must be very careful with the iterator interface.
alt2c::List<T> is implemented as a doubly linked circular
structure with floating head and tail nodes.
alt2d::List<T> is implemented as a doubly linked circular
structure with fixed head and tail nodes.
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/flist2c.cpp
LIB/tests/mlist2c.cpp
LIB/proj1/list2c.api
LIB/proj1/deliverables.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 four deliverables:
- A header file file
list2c.h defining the class
templates alt2c::List, alt2c::ConstListIterator,
and alt2c::ListIterator conforming to the specs below.
- A source code file list2c.cpp implementing the class templates
defined in the header file.
- A makefile named makefile2c with
targets flist.x, mlist.x, flist2c.x, mlist2c.x
along with the default target that builds all four.
- A text file
log.txt consisting of a log of all development activity, including
documentation for all testing.
All five 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 list2c.cpp.
Use these testing notes to create a summary testing report to conclude the file log.txt.
Turn in the
files name.com, list2c.h, list2c.cpp, makefile2c,
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 alt2c::List should follow the packaging methodology used for
fsu::List: a header file list2c.h and a "slave"
file list2c.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 circular doubly linked list plan with head
and tail nodes:
-
Define a full carrier to mean that tail_->next_ == head_. More generally,
the unused nodes are those between tail_ and head_. The number of such
nodes is the value returned by Excess(). The number of nodes between head_
and tail_ is the value returned by Size(). Capacity is the sum of size and
excess.
-
In general, this relation should always hold:
Capacity() + 2 = Size() + Excess() + 2 = n,
where n is the total number of nodes currently allocated.
-
Nodes between head_ and tail_ are those that contain List data and could at
times be called the "list side" of the carrier ring. Nodes between tail_ and
head_ are excess (allocated but not currently in use). These are sometimes called the "dark side" of the carrier ring.
- Pop and Remove operations always conserve allocated memory.
-
Implement PopFront() by advancing the head node: head_ = head_->next_ (only
when the list is not empty, of course). This conveniently "subtracts" a node
from the list and "adds" the node to the excess storage. Similarly, PopBack()
retreats the tail node.
- Remove(i) is a bit more tricky - the node should be unlinked from the list
and then linked behind the tail node for later re-use.
- Push and Insert operations re-use links whenever there are any available.
- When there are no excess nodes, Push and Insert operations operate exactly
as in the linear cases.
- When there is excess capacity, PushFront(tval) re-uses a node by retreating the
head node pointer: head_ = head_->prev_ and copying tval into the data field of the
old head node. Similarly, PushBack(tval) re-uses a node by advancing the tail_
node pointer. Insert(iter,tval) has to unlink the unused node following the tail_
node, link it in at the specified location, and update the data field.
- Size() is calculated by traversing from head_ to tail_. Excess() is
calculated by traversing from tail_ to head_.
Empty() is implemented as { return head_->next_ == tail_; }.
Full() is implemented as { return tail_->next_ == head_; }.
Required Functionality.
The functionality is outlined in the file list2c.api.
Be sure to run area51/flist2c_i.x to see the effects
of these List:: methods:
Clear() and Release() # 'c' and 'C' in the flist2c menu
Full() and Empty() # 'e'
Size(), Excess(), and Capacity() # 's'
The Dump() method ('d' in the flist2c menu) is useful in viewing the carrier
ring structure.
Required Runtime Constraints.
The runtime requirements are given in list2c.api.
Note that this means that we can use alt2c::List to
efficiently implement both Stack and Queue: Stack using PushFront/PopFront and
Queue using PushBack/PopFront (or PushFront/PopBack).
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 alt2c::List<T>, are supplied. Specific instructions for testing
for memory leaks are included as comment at the top of the file
mlist2c.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:
cpp/xran.h // the fsu::xran family of random object generators
cpp/xran.cpp // ...
cpp/xranxstr.h // ...
cpp/xranxstr.cpp // ... these are used by mlist.cpp and mlist2c.cpp
tcpp/list_sort.cpp // slave file for list2c.h
tcpp/list2c_macro.cpp // slave file for list2c.h
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 your makefile.
The file flist2c.cpp contains a typical "functionality"
test program. The idea is to provide access to the entire public interface of
class alt2c::List<> and alt2c::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
mlist2c.cpp
contains a dynamic test for correct memory and pointer management in the
implementation of alt2c::List<> and alt2c::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.
Your project makefile makefile2c should compile separate executables for
the client programs. You should also be able to compile the two supplied test
programs using this makefile by entering the command "make -f makefile2c all". You
can compile individual test programs or any other target in the makefile by
entering "make -f makefile2c xxx" where "xxx" is the target. For example,
"make -f makefile2c flist.x" creates the executable for flist.cpp and
"make -f makefile2c mlist.o" creates the object code for mlist.cpp.
The help flag is operational for flist.x
and flist2c.x: enter "flist.x -h" to get info on command line
arguments. These are optional file names. The first is an optional command
file. The second is an optional output file. When no argument is supplied, the
test operates purely interactively. When a command file is specified, the
commands in the file are
executed with the simulated keyboard entry displayed in red. When an output file is
specified, the colors are suppressed and all output is sent to the file. Note
that the 'x' option is intended to switch to interactive mode after a series of
commands is read from a file.
A good strategy to get started on the code for the project begins by
copying list2c.api to list2c.h and building the class
definition from that information.
Sample executables for flist, flist2c,
mlist, and mlist2c are available
in LIB/area51.
|