Spring 2025 CS 31

Programming Assignment 3
Tariffying Economics

Time due: 11:00 PM Wednesday, April 30

Introduction

The capricious King of Heard Island has changed his mind about how tariffs will be imposed on guano imports. His new scheme has the duty imposed being a percentage of the value of the shipment, with an individualized percentage for each country or territory of origin that might change over time depending on the status of negotiations with that country, the effect on the economy, or whether that country's leader said something about the king that he considers nice or nasty.

Since none of the king's subjects know how to program, you've been hired as part of a team to help with this. The percentages for various countries are encoded in a string along with indications whether each country's percentage is expected to go up or down in the near future. You will need to process that string. We will describe the format of the string and what you must do to process it.

First, we define some terms.

A letter is one of the 52 letter characters A through Z and a through z.

A country code is one of the following 250 two-letter codes, with each letter being in either upper or lower case (so US Us uS and us are all country codes): AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DK DJ DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GN GM GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UK UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW

A digit is one of the ten digit characters 0 through 9.

An expectation is one of these four letter characters: U u D d

A country record is a country code immediately followed by exactly one or two digits, immediately followed by an expectation. For example, FR7U and jP15d and in00U are country records; NZ 9U is not, because of the space character between the Z and the 9.

A tariff data string is a sequence of zero or more country records, with no character that is not part of a country record in that string. For example, FR7UjP15dNZ9Uin00U is a tariff data string consisting of four country records (FR7U, jP15d, NZ9U, and in00U); FR7U jP15d is not a tariff data string, because the space character between the U and the j is not part of any country record. The empty string is a tariff data string consisting of zero country records.

These are the semantics of a tariff data string: A tariff data string represents, for each country record in that string, data about the tariff for the country whose code is the country code in that country record. The digit(s) in that country code represent the percentage of the value of a shipment from the country that will be the duty imposed. (The king believes that a percentage of 99% is enough to discourage importers, so there was no need to define a country record to allow for more than two digits.) The expectation in the country record is U or u if it is expected that the percentage will go up in the near future, or D or d if it is expected to go down. For example, FR7UjP15d indicates that the percentage for the country FR is 7% and expected to go up, and the percentage for country jP (the same country that JP or Jp or jp represents) is 15% and is expected to go down.

Your task

Your assignment is essentially to take a tariff data string and compute some summary data about the tariffs represented in that string. For example, for the tariff data string FR7UjP15dNZ9Uin00U, the mean tariff percentage is 7.75% (the mean of 7, 15, 9, and 0), the number of tariff percentages expected to go up in the near future is 3, and the number expected to go down is 1.

For this project, you will implement the following two functions, using the exact function names, parameter types, and return types shown in this specification. (The parameter names may be different if you wish.)

bool isCorrectlyFormed(string tariffData)

This function returns true if its parameter is a tariff data string (i.e., it meets the definition above), or false otherwise.

int summarizeData(string tariffData, double& meanPercentage, int& numUp, int& numDown)

If the parameter tariffData is not a tariff data string (i.e., it does not meet the definition above), this function returns 1. If tariffData is a tariff data string, but a country record indicates a percentage of 99% and an expectation that the percentage will go up or it indicates a percentage of zero and an expectation that it will go down, this function returns 2. If the function returns 1 or 2, meanPercentage, numUp, and numDown must have the same values they had just before any statements in the function were executed. If none of the conditions for which the function returns 1 or 2 hold, then the function returns 0 after setting meanPercentage to the mean of the percentages indicated in the country records in tariffData (or 0 if there are no country records in tariffData), numUp to the number of country records indicating an expectation of up, and numDown to the number of country records indicating an expectation of down.

These are the only two functions you are required to write. (Hint: summarizeData may well call isCorrectlyFormed.) Your solution may use functions that you write in addition to these two if you wish. While we won't test those additional functions separately, using them may help you structure your program more readably.

Of course, to test the functions you write, you'll want to write a main routine that calls your functions. During the course of developing your solution, you might change that main routine many times. As long as your main routine compiles correctly when you turn in your solution, it doesn't matter what it does, since we will rename it to something harmless and never call it (because we will supply our own main routine to thoroughly test your functions).

There is a situation that would be important to deal with properly in the real world that we won't make you deal with to keep things simpler: If a tariff data string contains two or more country records for the same country, summarizeData does not have to work correctly. We guarantee we will not test that function with tariff data strings like FR7UjP15dfR26D (with FR and fR) or NZ9UNZ9U (with NZ and NZ).

Programming Guidelines

The functions that you write must not use any global variables whose values may be changed during execution. Global constants are allowed.

When you turn in your solution, neither of the two required functions, nor any functions you write that they call, may read any input from cin or write any output to cout. (Of course, during development, you may have them write whatever you like to help you debug.) If you want to print things out for debugging purposes, write to cerr instead of cout. cerr is the standard error destination; items written to it by default go to the screen. When we test your program, we will cause everything written to cerr to be discarded instead — we will never see that output, so you may leave those debugging output statements that write to cerr in your program if you wish.

The correctness of your program must not depend on undefined program behavior. Your program must never access out of range positions in a string. Your program must not, for example, assume anything about n's value at the point indicated, or even whether or not the program crashes:

    int main()
    {
        string s = "Hello";
        int n;              // n is uninitialized
        s.at(5*n/n) = '!';  // undefined behavior!
        …

Be sure that your program builds successfully, and try to ensure that your functions do something reasonable for at least a few test cases under both g31 and either Visual C++ or clang++. That way, you can get some partial credit for a solution that does not meet the entire specification.

If you wish, you may use this isValidUppercaseCountryCode.txt function as part of your solution. (We can't imagine why you would not want to use it, since it does some of the work of validating a supposed country code.)

You do not need to know anything about arrays to write this program. You may use arrays if you wish, but the most straightforward solutions to this project actually don't use arrays.

Your program must not use library facilities for matching regular expressions. (These usually involve "regex" somewhere in the name.) If you don't know what this paragraph is talking about, then there's nothing for you to worry about.

There are a number of ways you might write your main routine to test your functions. One way is to interactively accept test strings:

    int main()
    {
        for (;;)
        {
            cout << "Enter supposed tariff data string: ";
            string tds;
            getline(cin, tds);
            if (tds == "quit")
                break;
            cout << "isCorrectlyFormed returns ";
            if (isCorrectlyFormed(tds))
                cout << "true";
            else
                cout << "false";
            cout << endl;
        }
    }

While this is flexible, you run the risk of not being able to reproduce all your test cases if you make a change to your code and want to test that you didn't break anything that used to work.

Another way is to hard-code various tests and report which ones the program passes:

    #include <cmath>

    bool isNear(double a, double b)
    {
        return abs(a - b) < 1e-7;
    }

    int main()
    {
        if (isCorrectlyFormed("FR7UjP15dNZ9Uin00U"))
            cout << "Passed test 1: isCorrectlyFormed(\"FR7UjP15dNZ9Uin00U\")" << endl;
        if (!isCorrectlyFormed("ZZ7UjP15dNZ9Uin00U"))
            cout << "Passed test 2: !isCorrectlyFormed(\"ZZ7UjP15dNZ9Uin00U\")" << endl;
        double mean;
        int nUp;
        int nDown;
        mean = -999; nUp = -999; nDown = -999; // so we can detect if summarizeData sets these
        if (summarizeData("FR7UjP15dNZ9Uin00U", mean, nUp, nDown) == 0  &&
                isNear(mean, 7.75)  &&  nUp == 3  &&  nDown == 1)
            cout << "Passed test 3: summarizeData(\"FR7UjP15dNZ9Uin00U\", mean, nUp, nDown)" << endl;
        mean = -999; nUp = -999; nDown = -999; // so we can detect if summarizeData sets these
        if (summarizeData("ZZ7UjP15dNZ9Uin00U", mean, nUp, nDSown) == 1  &&
                mean == -999  &&  nUp == -999  &&  nDown == -999)
            cout << "Passed test 4: summarizeData(\"ZZ7UjP15dNZ9Uin00U\", mean, nUp, nDown)" << endl;
        …

This can get rather tedious. Fortunately, the library has a facility to make this easier: assert. If you include the header <cassert>, you can call assert in the following manner:

    assert(some boolean expression);

During execution, if the expression is true, nothing happens and execution continues normally; if it is false, a diagnostic message is written to cerr telling you the text and location of the failed assertion, and the program is terminated. Using assert, we can write the tests above more easily (This is a very incomplete set of test cases):

    #include <iostream>
    #include <string>
    #include <cmath>
    #include <cassert>
    using namespace std;

    bool isCorrectlyFormed(string tariffData)
    {
        … Your code goes here …
    }

    int summarizeData(string tariffData, char party, int& seatCount)
    {
        … Your code goes here …
    }

    bool isNear(double a, double b)
    {
        return abs(a - b) < 1e-7;
    }

    int main()
    {
        assert(isCorrectlyFormed("FR7UjP15dNZ9Uin00U"));
        assert(!isCorrectlyFormed("ZZ7UjP15dNZ9Uin00U"));
        double mean;
        int nUp;
        int nDown;
        mean = -999; nUp = -999; nDown = -999; // so we can detect if summarizeData sets these
        assert(summarizeData("FR7UjP15dNZ9Uin00U", mean, nUp, nDown) == 0  &&
            isNear(mean, 7.75)  &&  nUp == 3  &&  nDown == 1);
        mean = -999; nUp = -999; nDown = -999; // so we can detect if summarizeData sets these
        assert(summarizeData("ZZ7UjP15dNZ9Uin00U", mean, nUp, nDown) == 1  &&
            mean == -999  &&  nUp == -999  &&  nDown == -999);
        …
        cerr << "All tests succeeded" << endl;
    }

The reason for writing one line of output at the end is to ensure that you can distinguish the situation of all tests succeeding from the case where one function you're testing silently crashes the program.

What to turn in

What you will turn in for this assignment is a zip file containing these two files and nothing more:

  1. A text file named tariff.cpp that contains the source code for your C++ program. Your source code should have helpful comments that tell the purpose of the major program segments and explain any tricky code. The file must be a complete C++ program that can be built and run, so it must contain appropriate #include lines, a main routine, and any additional functions you may have chosen to write.

  2. A file named report.docx (in Microsoft Word format) or report.txt (an ordinary text file) that contains:
    1. A brief description of notable obstacles you overcame.
    2. A description of the design of your program. You should use pseudocode in this description where it clarifies the presentation.
    3. A list of the test data that could be used to thoroughly test your program, along with the reason for each test. You don't have to include the results of the tests, but you must note which test cases your program does not handle correctly. (This could happen if you didn't have time to write a complete solution, or if you ran out of time while still debugging a supposedly complete solution.) If you use the assert style above for writing your test code, you can copy those asserts, along with a very brief comment about what each is testing for. Notice that most of this portion of your report can be written just after reading the requirements in this specification, before you even start designing your program.

By April 29, there will be a link on the class webpage that will enable you to turn in your zip file electronically. Turn in the file by the due time above. Give yourself enough time to be sure you can turn something in. There's a lot to be said for turning in a preliminary version of your program and report early (You can always overwrite it with a later submission). That way you have something submitted in case there's a problem later.