Homework 7
Caution
  • You are expected to work individually.
  • Due: Friday April 12th at 11pm (Baltimore time).
  • This assignment is worth 70 points.

Learning Objectives

Learning Objectives
  • operator overloading
  • C++ templates
  • linked structures
  • dynamic memory allocation
  • recursion.
Starter Files

This homework comes with three starter source files: `CTree.h`, `CTreeTest.cpp`, and `TTreeTest.cpp`. You should get these files from the public repository under homework/hw7. Make sure you have a copy of these files before you start writing any code.

Part 1: CTree

Create a class called CTree which is a dynamic tree of characters. A tree is a data structure which begins with a root node containing data (a character in this case), and then links to children nodes (like a single parent family). Each child node in turn contains data and may have its own children. We refer to all the children of a particular node as siblings of each other. Other family terminology such as parent node, descendents, ancestors, etc. apply in the expected ways.

In an ordered tree, the siblings are always ordered according to some metric (ASCII ordering in this case). Our tree will also be special because the children of any particular node must contain unique data. So essentially the children form a set of data. Your class must be declared and implemented in files CTree.h and CTree.cpp, respectively. We will give you the CTree.h though. Visually, we may think of a character tree in a top-down fashion as depicted here. In this case ‘a’ is the root node of the entire tree, but each node is the root of the subtree below it. As such, trees are naturally recursive data structures. Note that the topmost root node in a tree should not have siblings.

           a
    /      |       \
  b        p         t
/      /   |   \   /   \
l     a    p    t  e    o
|     |    |            |
e     r    l            m
      |    | 
      t    e

An ordered character tree that is used in a particular way to store string information is called a “Trie” (pronounced “try”). You can store multiple strings from some text in a Trie by using a path from the root to a leaf node (a leaf node is one without any children) in order to spell each string in the text. For example, the strings stored in the tree above are “able”, “apart”, “apple”, “apt”, “ate”, and “atom”. Notice that by iterating through the nodes in a depth-first, left to right manner, we get an alphabetical ordering of the strings it represents. Your CTree class must have private data to store a character and its children information. Really we will treat each node of the tree as a (sub)tree itself. That way we only need one class instead of separate node and tree classes, and will be able to use recursion for much of the work. However, the implementation picture is somewhat more complicated than that above since we don’t know how many children any given node in the tree will have. Therefore, we will basically use a linked list to keep track of the children of a node, transforming the picture above into the following:

    a
    | 
    b   -   p         -         t
    |       |                   | 
    l       a   -  p  -  t      e   -    o
    |       |      |                     |
    e       r      l                     m
            |      |
            t      e

Here, each node keeps a link to its first child, which then links across to the next sibling in the chain, and also down to its own first child if it has one.The last node in a chain points to NULL. Each node should also keep a link to its preceding node. That is the parent if the node is a first child, or its left sibling if the node is not a first child. We are giving you the header file for this class in order to help with setting up the recursive structure properly.

Minimally, here are the functions that your CTree must have, keeping in mind that at all times the siblings of any node must be ordered and unique. You should only use the assignment =, equals ==, less than < and output << operators on chars to implement these functions. You are welcome to create helper functions and variations as well.

Caution
Remember that siblings must always be ordered.
  • Hint: the character version can simply create the node, call the other version and then return the resulting value.
  • Note: these functions can be useful in implementing the add functions.
Tip

The `CTreeTest.cpp` has an example on how this is supposed to work. look for the `testPlusOp()` function.

Regarding the toString() function, we want it to do a depth-first pre-order traversal of the tree. This means that as you iterate through the tree, the first node you get is the root, then it goes through that node’s children (recursively depth-first), and finally its siblings. As a related example, a simple (non-object-oriented) recursive depth-first traversal starting at some parameter node would look like this:

DFtraverse(node) {
    process data at node
    if node has children, 
       do a depth-first traversal from its first child
    if node has siblings (to the right), 
       do a depth-first traversal from its next sibling
}

You are given a file named CTreeTest.cpp that tests your implementation. Once you are done implementing CTree.cpp, you should compile and run CTreeTest to make sure that all test cases pass.

Part 2: (Templated) Tree

For the second and final part of the assignment you will generalize the CTree from part 1 to create a templated ordered tree with unique siblings. Note you will write your own header file for this part. Your tree class must be called Tree and be declared in Tree.h and fully defined (i.e., implementation of functions longer than one line should occur) in a file named Tree.inc. Note that the file has the .inc extension and not .cpp. The Tree.inc will be included at the end of the Tree.h file after the class declaration (This is because we can’t split templated classes into fully separate .h and .cpp files). Also, note the Tree class must be templated so that it works for various types as applicable.

Caution

Remember to keep data private, and const-protect objects (explicit and implicit parameters) whenever you can.

Your Tree class must contain the same public functions and overloaded operators as the CTree class, except that the parameters for the data stored in each node will now be of some generic type instead of char.

Because the templated Tree will need to keep siblings in order and unique, each base type will need to have the equals == operator and the less than < operator defined on them. If you used any operators other than these and the assignment operator = in the functions in CTree.cpp, you will need to rewrite those functions too.

Similar to the previous part, You are given a file named TTreeTest.cpp that tests your implementation (for the templated version). Once you are done implementing Tree.h and Tree.inc, you should compile and run tTreeTest to make sure that all test cases pass.

Rule of Three Clarification

As you know from the course materials, the Rule of Three states that whenever we have a non-trivial destructor, we should also implement a copy constructor and an assignment operator. In this homework, while we do ask you to implement a non-trivial destructor, we are not asking for you to implement the other two aspects.

Given this, please note that in your methods should be passing/receiving pointers or references to trees as opposed to tree objects directly. This is also true in the case of the + operator. While its syntax might suggest that it returns a new object, we expect you to return a reference to the already-existing tree on the left-hand side of the expression.

Makefile & Compiling

Create and submit a Makefile with two rules (aside, perhaps, from the clean rule):

  1. a rule that creates a target executable called cTreeTest for the first part of this homework.
  2. a rule that creates a target executable called tTreeTest for the second part of this homework (the templated version).

These two targets should compile with no errors or warnings using the typical C++ compilation command: g++ <source> -Wall -Wextra -std=c++11 -pedantic.

Caution

Do not use any special libraries that require different additional/different compiler options.

Danger

The autograder does an automatic check to see if it can compile the two targets. Thus, it is very important that you make sure your submission successfully compiles, otherwise your final score for the entire homework assignment will be 0.

Git log and README

In the assignments folder of your private repository, create a new subfolder named hw7. Do your work in that subfolder and use git add, git commit and git push regularly. Use git commit and the associated comments to document your work. e.g. if you just modified CTree.cpp to add a child functionality, you might do git add CTree.cpp; git commit -m "addChild functions added!"; git push.

Your submission includes a copy of the output of git log showing at least five commits to the repository. Save the git log output into a file called gitlog.txt i.e., by doing git log > gitlog.txt.

Also, please submit a file called README not README.txt or README.md, etc – just README including information about what extra helper functions you added (if any) and anything the graders should know about your submission. In the README you should:

Specific Requirements

Hints and Suggestions

Your submission to Gradescope

Create a .zip file named hw7.zip containing your source/header files, Makefile, gitlog.txt, and README. Do not include any .txt files and never submit any executable or object files!

Copy the hw7.zip file to your local machine (using scp or pscp), and submit it to Gradescope. When you submit, Gradescope conducts a series of automatic tests. These do basic checks, e.g. to check that you submitted the right files. If you see error messages (in red), address them and resubmit. You may resubmit any number of times prior to the deadline; only your latest submission will be graded. Review the course syllabus for late submission policies (grace period and late days).

Danger

Remember that if your final submitted code does not compile, you will earn a zero score for the assignment.

Info

Two notes regarding automatic checks for programming assignments:

  • Passing an automatic check is not itself worth points. (There might be a nominal, low point value like 0.01 associated with a check, but that won’t count in the end.) The checks exist to help you and the graders find obvious errors.
  • The automatic checks cover some of the requirements set out in the assignment, but not all. It is up to you to test your own work and ensure your programs satisfy all stated requirements. Passing all the automatic checks does not mean you have earned all the points.