Homework 1

Written Portion

A Few Notes on Repositories

  1. Never clone one repo into another. If you have a folder cs104 on your laptop (wherever you created your Github keys from Lab 0) and you clone your personal repo hw-username under it (i.e. cs104/hw-username) then whenever you want to clone some other repo, you need to do it back up in the cs104 folder or other location, NOT in the hw-username folder.
  2. Your repo is created when you register with our website (aka curricula system) as outlined in the Lab 0 writeup on the Labs Page. If you’ve followed those steps, accepted the invite to the Github organization that should be generated and emailed to you after you register with our website, and still cannot access your repository, you can then make a private post on the class Q&A to let your instructors know that your repository needs to be created. Be sure to include your USC username and github username for reference.

Skeleton Code

On many occasions we will want to distribute skeleton code, tests, and other pertinent files. To do this we have made a separate repository, resources, under our class GitHub site. You should clone this repository to your laptop (but only if you have not already done this as part of lab) and do a git pull regularly to check for updates.

$ git clone git@github.com:usc-csci104-summer2022/resources

Again, be sure you don’t clone this repo into your hw-username repo but at some higher up point like in a cs104 folder on your laptop. You can then manually copy (in your OS’s GUI or at the command line) the skeleton files from resources/hw1 to hw-username/hw1.

For example if you are in the folder containing both the resources and hw-username folders/repos, you could enter the following command at the terminal:

$ cp -rf resources/hw1 hw-username/

Again be sure to replace hw-username with your USC username (e.g. hw-ttrojan)

Problem 1 - Course Policies (6%)

Carefully study the information on the course web site, then answer the following questions about course policies:

Place your answers to this question in a file name hw1.txt in your hw-username/hw1 folder.

Part (a):

Which of the following are acceptable behaviors in solving homeworks/projects (list all that apply)?

  1. Looking up relevant C++ reference information online.
  2. Looking up or asking for sample solutions online from sites like Chegg, Github, etc.
  3. Talking to my classmates about general approaches about the problems (but no specific coding statements or description of your own code or someone else’s code).
  4. Copying code from my classmates or an online source, and then editing it significantly.
  5. Asking the course staff for help.
  6. Sitting next to my classmate and coding together as a team or with significant conversation about approach.
  7. Sharing my code with a classmate, even if he/she just wants to read over it and learn from it

Part (b):

To dispute a grade on a HW assignment you should (list all that apply):

  1. Email the professor immediately.
  2. Complete the regrade request form within 1 week of receiving the grade and wait for an issue to be posted to your Github repo.
  3. Visit the designated regrade TA within 1 week of your score posting.

Part(c):

What is the late submission policy (list all that apply)?

  1. One hour late submission is acceptable for each assignment.
  2. Each assignment can be submitted up to two days late for 50% credit.
  3. Each student has 5 late days of which only 2 can be used per HW
  4. Students need to get an approval before submitting an assignment late.

Part(d):

After pushing your code to Gihub you should… (list all that apply)

  1. Do nothing. Once you push your code you are done.
  2. Clone your repo to a temporary folder to ensure all the files you desire are pushed and that your code compiles.
  3. Complete the online submission page using your FULL (30 or more digit) SHA.

Part (e):

If you have NO grace days left and it is after the submission deadline, we will accept your assignment under what circumstances (list all that apply).

  1. None. We will not accept your submission.
  2. There is an hour grace period after the deadline.
  3. If there is a technical difficulty (such as wireless trouble, or github commit/push issues, etc.) with submission.
  4. If you email us your code as attachments.

Part (f):

General submission policies (indicate True/False).

  1. True/False: Before submitting your HW you should reclone your repo to a separate folder and ensure all files are present and your code compiles.
  2. True/False: If you forget to submit a file via GITHUB you can still apply for a regrade after the deadline and submit the missing file.
  3. True/False: You only have 7 days to submit a regrade for homeworks, unless otherwise stated, and after that you are not eligible for a regrade for ANY reason.

Problem 2 - Git (6%)

Carefully review and implement the steps discussed in Lab1. Then, answer the following questions:

Continue your answers to this question in the file name hw1.txt in your hw-username/hw1 folder.

Part (a):

When cloning a Git repo, which of the following should you avoid:

  1. Cloning into a folder that itself is a git repo
  2. Cloning into a sync’ed folder like Dropbox or Google Drive
  3. Cloning into the Desktop folder of your VM

Part (b):

Provide the appropriate git command to perform the following operations:

  1. Stage an untracked file to be committed. The file is called ‘hw1q2b.cpp’.
  2. Display the details of the last three commits in the repository.

Part (c)

What is the full command to re-clone your private CSCI104 repo to your VM? Assume you are in an appropriate folder.

Problem 3 - Runtime Analysis (24%)

In Big-Θ notation, analyze the running time of the following three pieces of code/pseudo-code. Describe it as a function of the input (here, n). Submit your answers as a PDF (using some kind of illustration software or scanned handwritten notes where you use your phone to convert to PDF) showing your work and derivations supporting your final answer. You must name the file q3_answers.pdf. As usual, answers without supporting work will receive 0 credit.

Part (a)

void f1(int n)
{
    int i=2;
    while(i < n){
        /* do something that takes O(1) time */
        i = i*i;
    }
}

Part (b)


void f2(int n)
{
    for(int i=1; i <= n; i++){
        if( (i % (int)sqrt(n)) == 0){
            for(int k=0; k < pow(i,3); k++) {
              /* do something that takes O(1) time */
            }
        }
    }
}

Part (c)

for(int i=1; i <= n; i++){
  for(int k=1; k <= n; k++){
    if( A[k] == i){
      for(int m=1; m <= n; m=m+m){
        // do something that takes O(1) time
        // Assume the contents of the A[] array are not changed
      }
    }
  }
}

Part (d)

Notice that this code is very similar to what will happen if you keep inserting into an ArrayList (e.g. vector). Notice that this is NOT an example of amortized analysis because you are only analyzing 1 call to the function f(). If you have discussed amortized analysis, realize that does NOT apply here since amortized analysis applies to multiple calls to a function. But you may use similar ideas/approaches as amortized analysis to analyze this runtime. If you have NOT discussed amortized analysis, simply ignore it’s mention.

int f (int n)
{
  int *a = new int [10];
  int size = 10;
  for (int i = 0; i < n; i ++) 
     {
        if (i == size)
          {  
             int newsize = 3*size/2;
             int *b = new int [newsize];
             for (int j = 0; j < size; j ++) b[j] = a[j];
             delete [] a;
             a = b;
             size = newsize;
          }
        a[i] = i*i;
     }
}

Problem 4 - Linked List Recursion Tracing (14%)

Consider the following C++ code.

All of the points for this problem will be assigned based on your explanation, since we have full faith in your ability to run this program and copy down the answer.

struct Node {
    int val;
    Node*  next;
};

Node* llrec(Node* in1, Node* in2)
{
    if(in1 == nullptr) {
        return in2;
    }
    else if(in2 == nullptr) {
        return in1;
    }
    else {
        in1->next = llrec(in2, in1->next);
        return in1;
    }
}

Question a: What linked list is returned if llrec is called with the input linked lists in1 = 1,2,3,4 and in2 = 5,6?

Question b: What linked list is return if llrec is called with the input linked lists in1 = nullptr and in2 = 2?

To show work, you can draw a call tree or box diagram of the function calls using some simplified substitution of your choice rather than pointer values (e.g. “p3” for a pointer to a node with value 3). Submit your answers as a PDF (using some kind of illustration software or scanned handwritten notes where you use your phone to convert to PDF) showing your work and derivations supporting your final answer. You must name the file q4_answers.pdf.

Problem 5 - NOT GRADED (PRACTICE ONLY) - Constructors and Destructors (0%)

Place your answers to the questions below in the same file as your answers to problem 1 and 2 (i.e. hw1.txt). You do not need to test and compile the code below. You are just writing out your answers in the hw1.txt file.

Suppose you were given the following class to model an entry in your contacts list which uses a custom Str class that models a string and replaces std::string.

#ifndef ENTRY_H
#define ENTRY_H
#include "str.h"

class Entry {
 public:
  Entry(const Str& name, const Str& phone);
  ~Entry();
  const Str& name() const;
  const Str& phone() const;

 private:
  Str name_;
  Str phone_;
};
#endif

Further assume that print statements are added to all constructor and destructors that print:

Question a: If the Entry constructor is as shown below, what will be printed when a new Entery object is constructed.

Entry::Entry(const Str& name, const Str& phone)
{
  cout << "Entry" << endl;
  name_ = name; 
  phone_ = phone;
}

Question b: If the Entry constructor is as shown below, what will be printed when a new Entry object is constructed.

Entry::Entry(const Str& name, const Str& phone)
  : name_(name), phone_(phone)
{
  cout << "Entry" << endl;
}

Now suppose a new Wrapper class is written that uses an Entry as a data member as shown below.

#ifndef WRAPPER_H
#define WRAPPER_H
#include "entry.h"
class Wrapper
{
 public:
  Wrapper(const Str& name, const Str& phone);
  void print() const;
 private:
  Entry e_;
};
#endif

Question c: Show how to complete the constructor such that the data member e_ is initalized with the arguments name and phone. Be sure to avoid any compile errors or runtime errors. You can always try your code out using a compiler. Show the entire constructor in your answer (i.e. start by copying the code below and then add to it).

// initialize e_ with name and phone
Wrapper::Wrapper(const Str& name, const Str& phone)
{

}

Programming Portion

Problem 6 - Sparse Matrix (Classes, Pointers, LinkedLists) (50%)

Be sure you copy over all the skeleton files in resources/hw1 that start with spmat to your hw-username/hw1 folder as well as the Makefile.

In this problem you will apply and deepen your linked list programming skills by creating a sparse matrix (ie. 2D array) of doubles.

Background

A sparse matrix is one where the majority of entries are 0. Due to this characteristic, we can save memory (and potentially time) by only storing non-zero entries. One approach to doing this is to use linked lists to only store the non-zero cells. Since we do not have an entry at every row/column index, each cell must store the row/column coordinates to which it corresponds along with its data value and necessary linked list pointers. So that we can scan quickly down either a row or a column we will choose to make each cell a member of two linked lists: a row linked list and a column linked list. Each list is a doubly-linked list to support faster insertions and removals. (You may wish to spend a moment considering why a singly-linked list would make insertion/removal less efficient). Thus the overall sparse matrix would resemble the diagram below for a 4x4 matrix. Notice, there is an array of head pointers for the row lists on the far left and another array of head pointers for the column lists on the top.

Provided Implementation

Much of the skeleton has been provided to you and you will need to provide the implementation of several of the key member functions. Please review it as you read this description.

We first use a simple Coord struct with a row and col member to track where entries belong in the 2D matrix. Next, we create a SparseItem struct to hold each non-zero cell’s data and, finally, a SparseMatrix class to model the entire matrix. The matrix can be of size n x n where n is provided when the SparseMatrix is constructed. Since, we have both row and column lists, we need to store the head pointers for the n rows and the n columns (for a total of 2*n head pointers) by using an array for each set of head pointers.

The header file is provided and is complete. You should not alter the public interface but may add private helper functions if you desire and potentially other data members (though this is not recommended and should likely be discussed with a course staff to see if there is a good alternative).

A few nested types have been defined to help with the implementation of the SparseMatrix:

Provided Functions

We have provided the following completed functions which you should NOT alter.

Additional Learning: Exceptions

Errors happen in programming. We receive unexpected or illegal inputs or arguments or we reach a state that we cannot handle. In those cases we can take some action like returning a specific value, but in some cases, based on the function signature, that may be impossible. An alternate approach is exceptions.

You should learn about exceptions by watching the provided lecture video and reading online or in the textbook.

Your Task

Study the class definition in the header file and the functions already implemented in the .cpp file. Use that code to understand how to interact with the head pointer lists, SparseItems, etc. and then implement the remaining functions (listed below). Note: You MUST adhere to the requirements of each function provided in the header file documentation. Failure to do so may result in no credit.

To be Implemented

For the SparseMatrix class, you will need to complete the following operations:

Private Helper function

Example

Suppose we create the following 4x4 SparseMatrix (n=4):

If we then perform a set operation on coordinate {0,0} with value 1.8, a new SparseItem should be created in that location and added to the appropriate linked lists, resulting in the following.

If we then perform a set operation on coordinate {1,2} to value 0, the SparseItem that in that location should be deleted (since we do not store values of 0). The resulting matrix is shown below.

Finally, if we perform a copyDim operation from row 3 to column 2 (i.e. SparseMatrix::copyDim({3,SparseMatrix::npos}, {SparseMatrix::npos,2}) ) then the old contents of column 2 should be removed (taking care not to delete an item that is also in the source dimension), and the elements from source row 3 should be copied into column 2. Note: the source dimension may be modifed by this operation when copying from one row/column to the opposite (as seen here).

Notes and Other Info

Testing

Many students try to rush this step. It is far easier to find bugs in your code for small test cases that you create (and understand) than with the larger test suites that we often provide. We strongly encourage you to read the test program spmat-test.cpp and sketch on paper what the matrix would look like and then run the code (adding additional print statements) to verify the operation of your implementation. If your code crashes or generates valgrind, that’s probably a GOOD thing initially because the small cases tested here will make solving your bugs easier. We ALWAYS encourage you to think of some simple, basic tests you can write for any data structure or problem you code in this class. And in doing so, think through what the expected output should be. Then if your code does not produce that, think about why (use gdb or add print statements). This is especially true when we release test suites. You MUST read over the test code and get an idea in your head of WHAT it is trying to do and WHAT the expected outputs are. Then when your code doesn’t produce the expected outputs, you’ll have an idea of where to start. PLEASE, taking testing seriously. The better you develop your debugging skills, the easier the following assignments will be.

 make debug

And then run ./spmat-test

To compile without the debug code enabled, you can just run:

 make

Remember, it is ALWAYS a good idea to run your tests through valgrind. You can do so by executing the following command line.

 valgrind --tool=memcheck --leak-check=yes ./spmat-test

Scroll through the output and look for invalid reads, writes, and the heap usage summary at the end. However, please note, that just as a doctor can only diagnose you based on the symptoms or the info you provide, valgrind can only check for errors based on what the test code exercises. If the test code never triggers code to test a function and there are memory leaks or invalid access in that function, valgrind will say no errors occurred. You are only as good as what your tests exercise, so it helps to write tests that will trigger each line of code in your class (this is often referred to as code coverage).

Submission Files

Ensure you add/commit/push all your source code files, Makefile, and written problem files. Do NOT commit/push any test suite folder/files that we provide from any folder other than the resources/hw1 repo.

WAIT You aren’t done yet. Complete the last section below to ensure you’ve committed all your code.

Commit then Re-clone your Repository

Be sure to add, commit, and push your code in your hw1 directory to your hw-username repository. Now double-check what you’ve committed, by following the directions below (failure to do so may result in point deductions):

  1. In your terminal, cd to the folder that has your resources and hw-username
  2. Create a verify-hw1 directory: $ mkdir verify-hw1
  3. Go into that directory: $ cd verify-hw1
  4. Clone your hw_username repo: $ git clone git@github.com:usc-csci104-summer2022/hw-username.git
  5. Go into your hw1 folder $ cd hw-username/hw1
  6. Switch over to a docker shell, navigate to the same verify-hw1/hw-username/hw1 folder.
  7. Recompile and rerun your programs and tests to ensure that what you submitted works. You may need to copy over a test-suite folder from the resources repo, if one was provided.