CSCI 161 Lab 1: sections S26N01/2

See the main lab page for submission deadlines and late penalties.

It is assumed you are keeping up to date with the lectures and labs.
Some additional git information is available through the git quick notes, git project/lab submission, and lecture resources pages.

This lab focuses on introducing our use of git for obtaining and submitting this year's labs and project, and also as a refresher on basic C++ programming.

In part 1 of lab 1, you'll be following the lab instructions to:

  1. create the markable (server-side) copy of the lab1 repository,
  2. clone that to create your own local working copy,
  3. compile the skeleton lab1 code using a provided makefile,
  4. revise, compile, and test your C++ code for lab1
  5. push your work back to the git server for marking

Once we've run through that cycle once, we'll start on part 2, the actual design and coding portion of the lab.
Details on the product requirements/specifications and design are in the second portion of this page.


Part 1: infrastructure

If you've taken 161 in the past then you probably already have a csci161 directory. You're not going to want to mix up this year's 161 stuff with the old stuff, so before you mkdir csci161 I'd suggest doing something like
mv csci161 old161

When doing the lab, take some time to ensure you understand what each command is supposed to do before you run it.

After running each command take some time to ensure you understand what actually did happen. (In the great scheme of things, it doesn't do anyone much good if you're just typing commands without understanding them.)

To reiterate: for this to work correctly, you will need to follow the lab instructions carefully and in the sequence given: take your time (we have lots!), read the description, run the command, and look at the results each command gives back before moving on to the next one.

Git instructions for obtaining, updating, and submitting the lab


0. double-check that the instructor has posted the repo
Run the command below, and check to make sure that the csci 161 lab1 repository appears in the list (if it isn't posted yet then don't proceed yet)
ssh -x csci info

This lists all the repositories you currently have access to, and the one you're specifically looking for will look like csci161/lab1.

A WARNING AGAINST TRYING THE REMAINING STEPS BEFORE YOUR LAB SESSION:
none of the remaining lab instructions will work correctly until the repository has been set up by the instructor, which may not happen until the day of the lab


1. create the server-side copy of the repo
To do this, enter the following command:
ssh -x csci fork csci161-01/lab1 csci161-01/$USER/lab1
(actually type $USER, as it is a pre-existing variable that contains your linux userid)

You can check if this worked correctly by running the ssh -x csci info command again, and it should now show an additional repository:
csci161-01/yourusername/lab1


2. clone your own local working copy
If you haven't previously done so, you'll want to create a csci161 directory in your home directory, i.e.
cd
mkdir csci161
cd csci161

now clone your server-side copy, creating a working lab1 repository here
git clone csci:csci161-01/$USER/lab1 lab1

We can check if this has been successful by entering our lab1 directory and checking which files are present:

cd lab1
ls
This should show lab1.cpp, lab1.h, ranking.h, ranking.cpp, test1.h, test1.cpp, makefile, and README


3. compile/run the two skeletal lab1x and test1x programs, and try a git push
We'll use the supplied makefile to compile the files and create a lab1x executable and then a test1x executable:
make all You can try running the executables (though they'll do nothing yet) using
./lab1x
./test1x

If you edit lab1.cpp and test1.cpp (use whatever editor you like), you'll see they each contain just an empty main and the #include for the lab1.h or test1.h.

In each of them, add the #include for the iostream and have a cout in main,
e.g. std::cout << "Hi!" << std::endl;

Save the files then from the command line recompile with
make all

Hopefully it recompiles test1.o, lab1.o, test1x, and lab1x

We'll try submitting your repository, just to make sure everything has been set up correctly. We'll git add the two files we edited and use git commit to take a snapshot of the current project state:

git add lab1.cpp
git add test1.cpp
git commit -m "added trial output to lab1, test1"
git push
(The commit will produce a couple of lines of messages about how many files were changed leading up to the commit and the create mode for lab1x.
The push will produce a handful of lines of output regarding compressing/writing objects, information about the remote in use, and ending with a large hexadecimal commit id and "master -> master".)

This will have copied your work back to the server where the instructor can access/mark it. You can repeat this cycle as often as you like, each time it sends the updated content as long as you remember to git add each of the files you've updated and do a git commit before your git push. (Thus the instructor will always have your most up to date version for marking.)

If anything goes wrong see step 4 for some basic trouble-shooting, and if that doesn't solve the problem then talk to your instructor for help.

The structural restrictions for the lab1 code are as follows:

Be sure you read, understand, and follow the code standards for CSCI 161, as they will most likely be different than the standards you were given in your CSCI 159/160 course. Marks will be deducted for failing to follow standards.

I also strongly recommend that after each successful series of modifications to your .h/.cpp files you do a
git add filename for each of the edited files
git commit -m "some message clearly and uniquely describing the changes you just made"

These use git to take and store a snapshot of your latest set of changes, which will subsequently allow you to roll back to the current state of your code if you ever need to (e.g. if you accidentally trash your lab1.cpp file later, or make changes you wish to undo).


4. Push your finished lab back to the git server
Assuming you've done the git adds and commits as discussed above, we need to push all your updates to the server for marking:
git push

Note that if you forget any of those steps (the adds, commit, or push) then the changes you just made will not reach the instructor for marking.

If you're ever in doubt about the current state of your repository you can run the command git status, which should eventually give a message like "up-to-date with origin master". Here are the most common other messages it may give you:

What to do if, on a push, you get an error like this:

FATAL: W any csci161-01/lab1 yourusername DENIED by fallthru
(or you mis-spelled the reponame)
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Note the push should be trying to go to "csci161-01/yourusername/lab1", not to "csci161-01/lab1" (which is the instructor repo), so this error message suggests that in step 2 you may have cloned the wrong repo, e.g. you may have done git clone csci:csci161-01/lab1
instead of the correct git clone csci:csci161-01/$USER/lab1
thus now in step 4 when you push it's trying to overwrite the instructor's version, so is giving you that permission error.

It's not an uncommon mistake, and easily fixed. We need your lab1 repo to think it was cloned from *your* space on the git server, not from the instructor's, so we'll reset where your repository thinks it came from using the following command:
git remote set-url origin csci:csci161-01/$USER/lab1

After that, you should be able to push normally.


If you ever want to completely start over:

  • you can get rid of your server-side repo with the command
    ssh csci D trash csci161-01/$USER/lab1
  • you can get rid of your local copy using the command sequence
    cd
    cd csci161
    rm -rf lab1
    

5. Understanding the file organization, #includes, and the makefile

Here we want to go over how/why we've broken the files up the way we have, how the #includes work to glue the parts together, and how the makefile compiles the individual .cpp files and links the resulting .o (object) files together to form working executables.

First, the organization of the files:

Next, the organization of the makefile:

Copy of the makefile contents
options= -Wall -Wextra -pedantic -g

all: lab1x test1x

lab1x: lab1.o ranking.o
	g++ lab1.o ranking.o -o lab1x ${options}

test1x: test1.o ranking.o
	g++ test1.o ranking.o -o test1x ${options}

lab1.o: lab1.cpp lab1.h ranking.h
	g++ lab1.cpp -c ${options}

test1.o: test1.cpp test1.h ranking.h
	g++ test1.cpp -c ${options}

ranking.o: ranking.cpp ranking.h
	g++ ranking.cpp -c ${options}

clean:
	rm -f lab1.o test1.o ranking.o lab1x test1x

Testing the makefile:



Second part: lab1 product requirements/specification/design

Here we'll finally focus on designing and implementing the various .h and .cpp files, i.e. the actual programming details for lab1.

We'll focus on this in several key stages:
    First, for the core application/product
  1. a general overview of the product we're trying to build,
  2. more specific details on particular aspects of the desired behaviour (from the user perspective),
  3. a high level design plan: the general structure of our solution/approach,
  4. profiles and specifications for the actual functions that will be required for our solution.

    Second, a unit-testing program:
    for lab1 this testing portion is optional, worth up to 30% bonus marks

  5. a general overview of what's desired in our testing program
  6. specific details on how the testing can be achieved
  7. a high level design plan: the structure of our solution/approach (for the tester),
  8. profiles and specifications for the actual functions that will be required for our solution (for the tester).

Once those are taken care of, our only remaining challenge will be to actually write, test, and debug the code!


    The product

  1. General product overview
    The idea for the product is to maintain a collection of rankings of different categories of things, e.g. rankings of movies, rankings of foods, rankings of restaurants, etc.
    Each item has an associated positive integer rank, typically from 1 to the low thousands, where higher ranks are better.
    Each category's ranks are stored in a separate file, e.g. the user might have a file named foods, a file named movies, etc.

    For lab1, the user will run the program, using a command line argument to provide the name of the file to be used, e.g.
    ./lab1x foods

    The program will read the items/rankings stored in the file, with the assumption that each line of the file contains the item's rank as a positive integer and then the item name is the rest of the text on the line, e.g.

    1830 hawaiian pizza
    12   brussel sprouts
    1900 salmon sashimi
    1750 those dutch olie bols things
    900 capsicum
    
    (no personal bias at all there anyplace in that example, nothing to see here, move along, move along)

    The program will then randomly pick two different items from the list and ask the user which of the two they would prefer right now (they can pick the first, the second, or a tie/draw). The rankings of both items will then be adjusted up/down based on the user's response, and the updated rankings written back to the original file.

  2. Detailed product specifications
    The areas where more detail is most crucially needed are in terms of: (i) exactly how is the question posed to the user and what are their valid responses, (ii) what precise formula is to be used in adjusting the ranks of the two items, and (iii) error handling expectations.

  3. Data definitions, function profiles and specifications
    The need to store a fixed collection of item names/ranks strongly suggests an array-of-structures approach, but that's not the only possible approach.

  4. High level design
    The sequence of actions we need to handle is essentially: (with the specified error handling at each stage)

    To decompose this into an appropriate suite of functions, divided in a suitable way across our files, we need to consider which actions/support are more associated with the rankings specifically (hence going into ranking.h/cpp) and which are more associated with the specific use of the rankings in this program (hence going into lab1.h/cpp).

    With that in mind, we can think of the actions we would like our lab1's main to perform, and sketch out what that might look like, e.g.

    int main(int argc, char* argv[])
    {
       // quit if the user didn't pass valid arguments
       if (!checkUsage(argc, argv)) {
          cerr << "Program terminated early, unable to proceed\n";
          return 0;
       }
    
       string filename = argv[1]; // filename is more intuitive later
       welcome(); // program intro for user
    
       // load the rankings from the named file
       ItemRank rankings[MaxItems];
       int numItems = readRankings(filename, rankings, MaxItems);
    
       // run a head-to-head matchup, quit if the matchup is unsuccessful
       if (!runMatchup(rankings, numItems)) {
          cerr << "Program terminated early, rankings file " << filename << " unchanged\n";
          return 0;
       }
    
       // write the updated rankings back to the file
       if (!writeRankings(filename, rankings, numItems)) {
          cerr << "Unable to updated rankings file " << filename << endl;
       }
    
       return 0;
    }
    

    If we decide the checkUsage and welcome belong in lab1.h/cpp and the others belong in ranking.h/cpp, and the actual definition of the struct needs to be in ranking.h, then our two .h files might look something like

    // the lab1.h, including a const for the maximum array size/number of items supported
    #pragma once
    #include "ranking.h"
    
    const int MaxItems = 50; // upper limit on the number of items to be read/stored/used
    
    // give the user a description of the program behaviour
    void welcome();
    
    // check a filename is non-empty and contains only valid characters
    // return true iff good
    bool okfilename(string fname);
    
    // check the user passed one argument, to be used as a filename
    bool checkUsage(int numArgs, char *arguments[]);
    
    
    // the ranking.h, with a few helper functions added to support the three core ones #pragma once #include <iostream> #include <string> using std::cout; using std::cin; using std::cerr; using std::endl; using std::string; const bool DEBUGMODE = false; // used to turn debugging output on/off // representation of a single item struct ItemRank { int rank; // positive integer rank string description; // text description of item }; // load item rankings from the named file, store in rankings, // max identifiers limit on number of rankings to be stored // return number of items stored int readRankings(string filename, ItemRank rankings[], int maxItems); // return true iff descript is a valid item description // (i.e. contains at least one non-whitespace character) bool validDescription(string descript); // run a head-to-head matchup // - pick distinct pair of items // - present user with choice, get valid response // - update rankings based on response // return true iff the matchup is successful bool runMatchup(ItemRank rankings[], int items); // write rankings to a file // return true iff successful bool writeRankings(string filename, ItemRank rankings[], int numItems); // calculate and return new rank for X based on current ranks for X and Y // and score from X's perspective: 1=X win, 0.5=draw, 0=Y win // return unchanged RankX in the event of an invalid parameter int calcNewRank(int RankX, int RankY, float score); // present the user with a choice between items X and Y // return 1 if they choose X // 0 if they choose Y // 0.5 if it's a draw // (repeat until they make a valid choice) float getChoice(ItemRank X, ItemRank Y);

    You're not required to use the design above, feel free to use it, or your own, or some hybrid.


    The unit tester
    again, this tester portion is an optional bonus (30%) for lab1

  5. General overview
    The idea for the unit testing program is to run a preset suite of tests against each of the functions provided in ranking.h/.cppp, allowing the tester to evaluate each for correctness.

  6. Detailed specifications
    For lab1 the goal is simply to start thinking about and applying testing in a structured, somewhat automated form. The specific nature of the test cases you apply, and how you execute them, is left open to you. Because of this, it is crucial that your comments in test1.h/.cpp clearly describe what you're testing and how.

  7. High level design
    Given the rather wide-open nature of the 'detailed specifications', the design is left to you. One simple starting point to run batches of tests on functions X, Y, and Z would be to have the main routine call one function for each batch, e.g.
    runXtests();
    runYtests();
    runZtests();
    
    Then you could separate the different issues associated with X, Y, and Z, and still have a relatively clean design.

  8. Data definitions, function profiles and specifications
    Again, the wide-open nature of the 'detailed specifications' means these aspects are left largely to you (beyond following the lab requirements for what belongs in a .h vs a .cpp etc). As mentioned earlier, good commenting of the testing code will be crucial.


Implementation and submission

Your task, by the lab submission deadline, is to complete all the functions detailed above and conforming to their specifications, and to do a final submission before the deadline.

Note that for the final submission, to be certain you're submitting all the relevant changes, I recommend the following:

git add lab1.cpp
git add lab1.h
git add test1.cpp
git add test1.h
git add ranking.cpp
git add ranking.h
git commit -m "final lab1 submission"
git push
Helpful starter code: command line arguments and file i/o

The code below is only a snippet of what's needed (and not arranged into our .h/.cpp design style), but it does show some of the use and error checking surrounding the use of command line arguments and relevant file reading.

#include <iostream>
#include <fstream>
#include <string>

// read rankings from contents of specified file (just echoes to screen)
void readRankings(char* fname)
{
   // attempt to open the specified file
   std::ifstream rfile;
   rfile.open(fname);

   // handle cases where it failed to open
   if (!rfile.is_open()) {
      std::cerr << "Unable to open input file " << fname << ", ending program" << std::endl;
   }

   // read file contents line by line, assuming each line is rank then the description
   do {
      std::string rank;
      std::string description;
      rfile >> rank;  // get the next rank (the first word on the next non-empty line)
      if (!rfile.eof()) {
         // not end-of-file, so we did actually get a rank, now get the description
         getline(rfile, description); // rest of the line
         if (!rfile.eof()) {
            // not end-of-file, so we did actually get something (though maybe just eoln)
            std::cout << "Rank: " << rank << ", description: " << description << std::endl;
         } else {
            // hit end of file in mid-line
            std::cerr << "Error: end of file after rank " << rank << std::endl;
         }
      }
   } while (!rfile.eof());

   rfile.close();
}

int main(int argc, char* argv[])
{
   // check the correct number of arguments are passed (at least one filename)
   if (argc < 2) {
      std::cerr << "Incorrect usage, expecting: " << argv[0] << " filename" << std::endl;
   } else {
      // pass the filename to readRankings to attempt a read
      readRankings(argv[1]);
   }
}


SAMPLE RUNS

A number of sample runs of the lab1x program are shown below, starting with the command the user enters to start the lab1x program running and showing the file content before and after the run. (For the sake of clarity in the example, user input is shown in bold italics - this is of course not something your program actually could/would do.)

For these sample runs I've created a directory named "tests" in my lab1 directory (mkdir tests) and created a handful of different testfiles to try out the program. You can do the same using mkdir tests and then your preferred editor to make a bunch of files in it.

Your screen output doesn't have to exactly match what's shown below as long as the general level of information provided for the user is similar, but the file content changes should match as shown (the order of items and values).

Program run Original file File afterward
./lab1x tests/posted 
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
 
Given the choice between the following two items: 
   First:      capsicum 
   Second:     salmon sashimi 
please enter F for first, S for second, or D for draw (tied) 
F 

Updated rankings written back to file tests/posted
1830    hawaiian pizza
12    brussel sprouts
1900    salmon sashimi
1750    those dutch olie bols things
900    capsicum
1830     hawaiian pizza
12     brussel sprouts
1885     salmon sashimi
1750     those dutch olie bols things
915     capsicum
./lab1x tests/single 
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
Error: unable to run a head-to-head matchup as there are fewer than two items 
Program terminated early, rankings file tests/single unchanged 
1200 justthis
1200 justthis
./lab1x tests/nothing
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
Error: unable to run a head-to-head matchup as there are fewer than two items 
Program terminated early, rankings file tests/nothing unchanged 
(the file is empty)
(the file is empty)
./lab1x tests/nosuchfile 
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
Error: unable to open file tests/nosuchfile for reading 
Error: unable to run a head-to-head matchup as there are fewer than two items 
Program terminated early, rankings file tests/nosuchfile unchanged 
(the file doesn't exist)
(the file doesn't exist)
./lab1x tests/nodesc 
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
Error: invalid description, discarding line 
   1750 
 
Given the choice between the following two items: 
   First:      salmon sashimi 
   Second:     brussel sprouts 
please enter F for first, S for second, or D for draw (tied) 
S

Updated rankings written back to file tests/nodesc
1830    hawaiian pizza
12    brussel sprouts
1900    salmon sashimi
1750
900    capsicum
1830     hawaiian pizza
27     brussel sprouts
1885     salmon sashimi
900     capsicum
./lab1x tests/norank 
 
Welcome to the lab1 item ranking program 
This program reads a collection of item rankings from a file 
   (whose name is provided as a command line argument, e.g. ./lab1x foods) 
Presents you with a choice between two items, 
and uses your preference to update their rankings (rewriting the file) 
 
Error: invalid rank, discarding line 
   brussel sprouts 
 
Given the choice between the following two items: 
   First:      capsicum 
   Second:     salmon sashimi 
please enter F for first, S for second, or D for draw (tied) 
D

Updated rankings written back to file tests/norank
1830    hawaiian pizza
brussel sprouts
1900    salmon sashimi
1750    those dutch olie bols things
900    capsicum
1830     hawaiian pizza
1893     salmon sashimi
1750     those dutch olie bols things
907     capsicum