Monday, July 31, 2017

The Little Lesson of C++ Horrors

Dear readers, it is a particular pleasure to welcome you, or welcome you back, with another of those so called 'Chapter Summaries'. I was very interested in the topic of this chapter, because this is the heart and soul of almost any program or game: They interact with files in one way or other. When starting out with it, I was also curious as to which Challenges this chapter would offer me, and how challenging the Programming Challenges would be.

Well, as it soon turned out, the Challenges in this chapter would be very difficult to solve. In some few cases it was an endless chain of errors, mistakes, bad design decisions, numerous - 'Oh, no ... PLEASE ... I do not want to start over again ...' moments. This was in part due to the nature of the problems to be solved, but also mainly due to a heatwave so temperatures would be as high as 39°C. At some occasions I didn't even manage to write a single blog-post, asking for apology for making a mistake, or giving an explanation as to why I had to delete an already submitted code, without constantly trying to write 9.5 instead of 12.5 or 9.6 instead of 12.6. It was so bad that it has taken almost 10 minutes to get it right.

But the real horror would wait for me until the final moments before uploading the code written for Programming Challenge 12.16. Imagine this, all there was left to do was to switch some parts of code around, I was watching Полина Гагарина - Кукушка (OST Битва за Севастополь) over at youtube at that moment, (Polina Gagarina - Cuckoo (OST Battle for Sevastopol))


A moment later a BSOD would appear on my screen, and the PC restarts. The IDE was open, the progress wasn't saved, and when the reboot process was complete, and the IDE re-opened, guess what happened? A dialog appeared asking whether the project should be restored. Of course, restore it for me Visual Studio, go ahead, do! Then my heart stopped, as instead of having the code last saved 10 minutes or so ago before the BSOD happened, a text-file opened. An empty text-file. All work was gone, hours and hours of work wasted? No, lucky me, I was wise enough to upload some backups setting me back an hour or so, so not much was lost. Still it hurt. 

Trying to solve the main problem of this challenge, which was to find a way to delete a record from a file, working hours upon hours to make code 'look good', to make it 'feel right', then this? Also this should be one of the best programs after all the disaster of code I uploaded for some of the Challenges before that ... It just wasn't right. 

Even though I'm not altogether happy, as I think many of the codes could have been more polished, more complete, better written, more sophisticated than they are, not all about this lesson and its Challenges was all bad. For instance the  Inventory Program Challenge. Even though I had to change the code and re-upload it, it was probably one of the ones I most enjoyed solving. Themed around Perry Rhodan, which some of my German and Austrian visitors might be familiar with, makes it something special, or more personal than it would otherwise have been.

Anyway, I got past all the challenges, one in particular with the help of a dreamincode.com member, who was so very kind as to provide a complete code example I was able to draw inspiration from. I've been very lucky to receive so much input, and the patience of that particular community member. All the others I had to overcome by myself. Something I can be a little proud of. 

So, what comes next? Of course! Another lesson! But there is more to come than just a lesson about Classes in C++ to come. I will probably make a switch from the 8E version of Starting out With C++ From Control Structures through Objects to 9E which I bought half a month ago. The main difference will probably be in the number of Programming Challenges to be solved, and of course the lessons themselves have been updated in numerous spots. Speaking of which, since there are some new Challenges to be found in the earlier chapters, I will solve those and upload the result. 

Now it is time to take a little rest. As always I would like all those among you who found the way to this humble blog a great time, and I hope you found what you came here looking for. To my fellow learners, I hope you'll soon be able to finish this lesson if you haven't already, without to much horror and trouble lying in wait for you ... As for me, I will be back soon, with more code, more horror-stories, and probably more gray hair on my head than I am able to count.

Programming Challenge 12.16 - Customer Accounts

Example Files: AshikagaAcc.dat
                         AshikagaAccOrig.dat
                         EvalData.h
                         UtilityCls.h


/* Customer Accounts - This program uses a structure to store the following
      data about a customer account:
  
        * Name
        * Address
        * City, State and ZIP
        * Telephone Number
        * Account Balance
        * Date of Last Payment

    The structure is used to store customer account records. The program has
    a menu that lets the user perform the following operations:

        * Enter new records into the file.
        * Search for a particular customer's record and display it.
        * Search for a particular customer's record and delete it.
        * Search for a particular customer's record and change it.
        * Display the contents of the entire file
  
    Input Validation: When the data for a new account is entered, it is made
    sure that data for all the fields has been entered. No negative account
    balances are allowed. */

#include "UtilityCls.h"
#include "EvalDate.h"

const int NAME_SIZE = 20;
const int ADDRESS_SIZE = 20;
const int NUM_SIZE = 12;
const int DATE_SIZE = 11;

struct AccountData
{
    char          accNumber[NUM_SIZE];
    char          name[NAME_SIZE];
    char          address[ADDRESS_SIZE];
    char          city[ADDRESS_SIZE];
    char          state[ADDRESS_SIZE];
    char          zipCode[NUM_SIZE];
    char          telephoneNumber[NUM_SIZE];
    double      accountBalance;
    char          dateLastPayment[DATE_SIZE];

    /* Inventory Destructor */
    ~AccountData()
    {
    }
};

struct Date
{
    int addDay;        /* Holds day   [1-31] */
    int addMonth;    /* Holds month [1-12] */
    int addYear;    /* Holds year             */

    Date()
    {
        addDay = 0;
        addMonth = 0;
        addYear = 0;
    }

    ~Date()
    {
    }
};

enum class MenuItems
{
    ADD_RECORD = 'A', DISPLAY_RECORD = 'D', DISPLAY_ALL = 'C', EDIT_RECORD = 'E',
    DELETE_RECORD = 'R', QUIT = 'Q'
};

enum class Choice
{
     YES = 'Y', NO = 'N'
};

void   menu(AccountData &, const string);
char     menuItems(const char, const char, const char,
                  const char, const char, const char);
string getFileName();
void   processRecord(AccountData &, const char, const char, const string);
void     getAccountData(AccountData &);
int    writeRecord(AccountData &, const string);
void   getDate(AccountData &);
string dateToString(const int, const int, const int);
int    readRecord(AccountData &, const string);
int    editRecord(AccountData &, const char, const char, const string);
int    deleteRecord(AccountData &, const char, const char, const string);
void   displayRecord(AccountData &);
int    displayAll(AccountData &, const string);
int    removeRec(AccountData &, fstream &, const string, const string, int);
char   getChoice(const char, const char);

int main()
{
    AccountData accounts;
    string fileName = "";

    cout << "\nASHIKAGA BANK - ACCOUNT MANAGER\n\n";
    fileName = getFileName();

    clearScreen();
    menu(accounts, fileName);

   pauseSystem();
   return 0;
}

/* **********************************************************
    Definition: menuItems

    This function presents a menu-screen to the user from
    which he or she can select one of the available options.
    ********************************************************** */

char menuItems(const char addRec, const char dispRec, const char dispAll,
                   const char editRec, const char delRec, const char quit)
{
    char menuItem = ' ';

    cout << "\n\tASHIKAGA BANK - ACCOUNT MANAGER\n\n";
    cout << "\t[A] - [ ADD         ] CUSTOMER ACCOUNT RECORD\n"
            << "\t[D] - [ DISPLAY     ] CUSTOMER ACCOUNT RECORD\n"
          << "\t[C] - [ DISPLAY ALL ] CUSTOMER ACCOUNT RECORDS\n"
          << "\t[E] - [ EDIT        ] CUSTOMER ACCOUNT RECORD\n"
          << "\t[R] - [ REMOVE      ] CUSTOMER ACCOUNT RECORD\n"
          << "\t[Q] - [ QUIT        ]\n\n";
    cout << "\tSELECT MENU ITEM: ";  
    cin >> menuItem;

    menuItem = toupper(menuItem);

    while (menuItem < addRec && menuItem > quit)
    {
        cout << "\n\tMenu item " << menuItem << " does not exist.\n\n"
              << "\tYour selection: ";
        cin >> menuItem;
    }

    return menuItem;
}

/* **********************************************************
    Definition: menu

    This function accepts a structure variable passed to it by
    reference as its argument. It provides a menu structure
    that allows the user to select from the following items:

        * Add a record
        * Display a single customer account record
        * Display all customer account records
        * Edit a customer record
        * Delete a customer record
        * Quit
    ********************************************************** */

void menu(AccountData &accounts, const string fileName)
{
    const char addRec  = static_cast<char>(MenuItems::ADD_RECORD);
    const char dispRec = static_cast<char>(MenuItems::DISPLAY_RECORD);
    const char dispAll = static_cast<char>(MenuItems::DISPLAY_ALL);
    const char editRec = static_cast<char>(MenuItems::EDIT_RECORD);
    const char delRec  = static_cast<int>(MenuItems::DELETE_RECORD);
    const char quit    = static_cast<int>(MenuItems::QUIT);

    const char positive = static_cast<char>(Choice::YES);
    const char negative = static_cast<char>(Choice::NO);

    int  selection = 0;

    do
    {
        clearScreen();
        selection = menuItems(addRec, dispRec, dispAll, editRec, delRec, quit);

        switch (selection)
        {
            case addRec:
            {
                clearScreen();
                cout << "\n\tASHIKAGA BANK - ADD CUSTOMER ACCOUNT RECORD\n\n";
                processRecord(accounts, positive, negative, fileName);
            } break;

            case dispRec:
            {
                clearScreen();
                cout << "\n\tASHIKAGA BANK - DISPLAY SINGLE CUSTOMER ACCOUNT RECORD\n\n";
                readRecord(accounts, fileName);
                cin.ignore();
            } break;

            case dispAll:
            {
                clearScreen();
                cout << "\n\tASHIKAGA BANK - DISPLAY ALL CUSTOMER ACCOUNT RECORDS\n\n";
                displayAll(accounts, fileName);
                pauseSystem();

            } break;

            case editRec:
            {          
                clearScreen();
                cout << "\n\tASHIKAGA BANK - EDIT CUSTOMER ACCOUNT RECORD\n\n";
                editRecord(accounts, positive, negative, fileName);
            } break;

            case delRec:
            {  
                clearScreen();
                cout << "\n\tASHIKAGA BANK - DELETE CUSTOMER ACCOUNT RECORD\n\n";
                deleteRecord(accounts, positive, negative, fileName);
            } break;

            case quit:
            {
                cout << "\n\tASHIKAGA BANK - CUSTOMER ACCOUNT SYSTEM LOGOUT\n\t"
                      << "Remember Policy: Customer First!";
            } break;
        }
    } while (selection != quit);
}

/* **********************************************************
    The user is asked for a filename, which is returned from
    this function.
    ********************************************************** */

string getFileName()
{
    string fileName = "";

    cout << "\nEnter the name of the file to store and retrieve\n"
          << "customer account data: ";
    cin >> fileName;

    return fileName;
}

/* **********************************************************
   Definition: getAccountData

    This function accepts a struct variable passed to it by
    reference as its argument. The user is asked to enter data
    for a customer.
   ********************************************************** */

void getAccountData(AccountData &accHolder)
{
    cout << "\nEnter the following customer account data\n\n";
    cout << "Account Number [123-456-789]: ";
    cin.ignore();
    cin.getline(accHolder.accNumber, NUM_SIZE);

    while (strlen(accHolder.accNumber) == ' ' ||
            strlen(accHolder.accNumber) < NUM_SIZE-2)
    {
        cout << "Account Number [123-456-789]: ";
        cin.ignore();
        cin.getline(accHolder.accNumber, NUM_SIZE);
    }
  
    cout << "Customer Name: ";
    cin.getline(accHolder.name, NAME_SIZE);

    while (strlen(accHolder.name) == '\0')
    {
        cout << "Customer Name: ";
        cin.getline(accHolder.name, NAME_SIZE);
    }

    cout << "Address: ";
    cin.getline(accHolder.address, ADDRESS_SIZE);

    while (strlen(accHolder.address) == '\0')
    {
        cout << "Address: ";
        cin.getline(accHolder.address, ADDRESS_SIZE);
    }

    cout << "City: ";
    cin.getline(accHolder.city, ADDRESS_SIZE);

    while (strlen(accHolder.city) == '\0')
    {
        cout << "City: ";
        cin.getline(accHolder.city, ADDRESS_SIZE);
    }

    cout << "State: ";
    cin.getline(accHolder.state, ADDRESS_SIZE);

    while (strlen(accHolder.state) == '\0')
    {
        cout << "State: ";
        cin.getline(accHolder.state, ADDRESS_SIZE);
    }

    cout << "Zip-Code: ";
    cin.getline(accHolder.zipCode, ADDRESS_SIZE);

    while (strlen(accHolder.zipCode) == '\0')
    {
        cout << "Zip-Code: ";
        cin.getline(accHolder.zipCode, NUM_SIZE);
    }

    cout << "Telephone # ";
    cin.getline(accHolder.telephoneNumber, NUM_SIZE);

    while (strlen(accHolder.telephoneNumber) == '\0')
    {
        cout << "Telephone # ";
        cin.getline(accHolder.telephoneNumber, NUM_SIZE);
    }

    cout << "Account Balance: JPY ";
    cin >> accHolder.accountBalance;

    while (accHolder.accountBalance <= 0)
    {
        cout << "Account Balance: JPY ";
        cin >> accHolder.accountBalance;
    }
  
    getDate(accHolder);
}

/* **********************************************************
   Definition: getDate

    This function accepts a structure variable passed to it by
    reference as its argument. It asks and evaluates the date
    entered. This information is stored in a member variable
    of the Inventory structure.
   ********************************************************** */

void getDate(AccountData &accHolder)
{
    Date addDate;

    cin.ignore();
    cout << "\nDate added:\n";
    cout << "Day: ";
    cin >> addDate.addDay;

    cout << "Month: ";
    cin >> addDate.addMonth;

    cout << "Year: ";
    cin >> addDate.addYear;

    while (validateDate(addDate.addDay, addDate.addMonth, addDate.addYear) == false)
    {
        cout << "\nInvalid date detected.";
        cin.clear();

        cout << "\nDate added:\n";
        cout << "Day: ";
        cin >> addDate.addDay;

        cout << "Month: ";
        cin >> addDate.addMonth;

        cout << "Year: ";
        cin >> addDate.addYear;
    }

    string vDate = dateToString(addDate.addDay, addDate.addMonth,
                                         addDate.addYear);

    strcpy_s(accHolder.dateLastPayment, DATE_SIZE, vDate.c_str());
}

/* **********************************************************
   Definition: dateToString

    This function accepts three integer values as arguments.
    A stringstream object is used to store the date in a
    specific format, which is returned from the function.
   ********************************************************** */

string dateToString(const int dd, const int mm, const int yy)
{
    stringstream dateStream;

    if (dd < 10 && mm < 10)
    {
        dateStream << "0" << dd << "/0" << mm << "/" << yy;
    }
    else if (dd >= 10 && mm < 10)
    {
        dateStream << dd << "/0" << mm << "/" << yy;
    }
    else
    {
        dateStream << dd << "/" << mm << "/" << yy;
    }

    return dateStream.str();
}

/* **********************************************************
   Definition: processRecord

    This function accepts a structure variable passed to it by
    reference and a filename as its arguments. It calls two
    functions:

        * getItemInfo()
        * writeRecord()

    As long as the user decides that he or she wishes to add
    new customer account records, these function are executed.
    If his or her answer is 'N', the function will exit to the
    main menu.
   ********************************************************** */

void processRecord(AccountData &accHolder, const char positive,
                         const char negative, const string fileName)
{
    char choice = ' ';

    do
    {
        getAccountData(accHolder);
        writeRecord(accHolder, fileName);

        cout << "\nDo you wish to add another customer record? \n";
        cin.ignore();
        cin.get(choice);
        cout << "\n";

        choice = toupper(choice);

        while (toupper(choice) != positive && toupper(choice) != negative)
        {
            cout << "\nDo you wish to add another customer record? ";
            cin.ignore();
            cin.get(choice);
            cout << "\n";
        }
    } while (choice != negative);
}

/* **********************************************************
   Definition: writeRecord

    This function accepts a structure variable passed to it
    by reference and a filename as its arguments. It tries to
    open a file in binary write mode. Upon success, data is
    written in append mode to the file. If an error occurs, a
    message is displayed and the function exits to the menu.
   ********************************************************** */

int writeRecord(AccountData &accHolder, const string fileName)
{
    fstream writeRec(fileName, ios::out | ios::binary | ios::app);

    if (!writeRec.fail())
    {
       writeRec.write(reinterpret_cast<char *>(&accHolder), sizeof(accHolder));

        cout << "\nAccount data successfully written to\n"
              << fileName << " \n";
    }
    else
    {
        cout << "FILE ERROR: Could not write account data to " << fileName << "\n"
              << "Returning to main menu ...\n";
    }
    writeRec.close();

    return 0;
}

/* **********************************************************
    Definition: getChoice

    This function asks the user if he or she wishes to add
    another customer account record. The choice is validated
    and returned to the caller.
    ********************************************************** */

char getChoice(const char positive, const char negative)
{
    char choice = ' ';

    cout << "\n\n\tDo you wish to add another customer account record? ";
    cin >> choice;
    cin.ignore();

    /* Input validation */
    while (toupper(choice) != positive && toupper(choice) != negative)
    {
        cout << "\n\tDo you wish to add another customer account record? ";
        cin >> choice;
        cin.ignore();
    }

    return toupper(choice);
}

/* **********************************************************
   Definition: readRecord

    This function accepts a structure variable passed to it by
    reference and a filename as its arguments. It tries to
    open a file to read data back in. Upon success, the user
    is first asked to enter a record number. The position of
    this record is retrieved and the record is displayed. In
    case of an error, a message is displayed, and the function
    exits to the main menu.
   ********************************************************** */

int readRecord(AccountData &accHolder, const string fileName)
{
    long recNum = 0;

    fstream readRec(fileName, ios::in | ios::binary);

    /* Upon success, the item record conforming to the input made
    by the user is retrieved, and the item information is
    displayed. */
    if (!readRec.fail())
    {
        cout << "\nEnter Customer Account Number: ";
        cin >> recNum;
        cin.ignore();
        readRec.seekg((recNum - 1) * sizeof(accHolder));
        readRec.read(reinterpret_cast<char *>(&accHolder), sizeof(accHolder));

        displayRecord(accHolder);
    }
    else
    {
        cout << "\nFILE ERROR: Data could not be read from" << fileName << " ...\n"
              << "Returning to main menu ...\n";
        return -1;
    }
    readRec.close();

    return 0;
}

/* **********************************************************
   Definition: displayAll

    This function accepts a structure variable passed to it by
    reference and a filename as its arguments. Upon succes,
    the customer account data is read in and displayed. If an
    error occurs, a message is displayed, and the function
    exits to the main menu.
   ********************************************************** */

int displayAll(AccountData &accHolder, const string fileName)
{
    fstream readRec(fileName, ios::in | ios::binary);

    /* Upon success, the item record conforming to the input made
    by the user is retrieved, and the item information is
    displayed. */
    if (!readRec.fail())
    {
        while(readRec.read(reinterpret_cast<char *>(&accHolder), sizeof(accHolder)))
        displayRecord(accHolder);
    }
    else
    {
        cout << "\nFILE ERROR: Data could not be read from" << fileName << " ...\n"
              << "Returning to main menu ...\n";
        return -1;
    }

    readRec.close();

    return 0;
}

/* **********************************************************
   Definition: editRecord

    This function accepts a structure variable passed to it
    by reference, two char variables, and a filename as its
    arguments. It tries to open a file in read and write mode.
    Upon succes, the user is asked to enter the record number
    he or she wishes to change. This information is retrieved,
    the record read in and displayed.
  
    The user is then asked if this is the record he or she
    wishes to edit. If the answer is positive, a function that
    allows the user to enter data is called. When done, the
    user is asked whether the information he or she entered
    is correct. If the answer is positive, the item record is
    written to file and the function will exit.

    In case the user decides that he or she does not wish to
    either change a particular record, or finds the record
    information is incorrect, the function will exit to the
    main menu.
   ********************************************************** */

int editRecord(AccountData &accHolder, const char positive,
                    const char negative, const string fileName)
{
    long recNum = 0;
    char choice = ' ';

    fstream alterRec(fileName, ios::in | ios::out | ios::binary);

    if (!alterRec.fail())
    {
        cout << "\nEnter Customer Account Number: ";
        cin >> recNum;

        alterRec.seekg((recNum -1) * sizeof(accHolder));
        alterRec.read(reinterpret_cast<char *>(&accHolder), sizeof(accHolder));

        /* Display the customer account information */
        displayRecord(accHolder);

        cout << "Do you wish to change this customer account record? ";
        cin >> choice;
        cout << "\n";

        choice = toupper(choice);

        while (toupper(choice) != positive && toupper(choice) != negative)
        {
            cout << "Do you wish to change this customer account record? ";
            cin >> choice;
            cout << "\n";
        }

        if (toupper(choice) == positive)
        {
            /* Get new item information */
            getAccountData(accHolder);

            /* Moves to the position the item record is stored at. */
            alterRec.seekp((recNum - 1) * sizeof(accHolder), ios::beg);

            /* The user is asked to confirm his or her choice before the changed
                record is written to the file. */
            cout << "\nIs this information correct? ";
            cin >> choice;
            cout << "\n";

            choice = toupper(choice);

            while (toupper(choice) != positive && toupper(choice) != negative)
            {
                cout << "\nIs this information correct? ";
                cin >> choice;
                cout << "\n";

                choice = toupper(choice);
            }

            if (toupper(choice) == positive)
            {
                alterRec.write(reinterpret_cast<char *>(&accHolder), sizeof(accHolder));
            }
        }
        else
        {
            cout << "\nChoice confirmed. No data has been changed.\n"
                  << "Returning to main menu ...\n";
        }
    }
    else
    {
        cout << "\nFILE ERROR: Data could not be read from or written to " << fileName << "\n"
              << "Now returning to main menu ...\n";
        return -1;
    }
    alterRec.close();

    return 0;
}

/* **********************************************************
   Definition: deleteRecord

    This function accepts a structure variable passed to it
    by reference and a filename as its arguments. It tries to
    open a file in binary read and write mode. Upon success,
    the data is read into memory, and the user is asked to
    enter the number of the record he or she wishes to delete.
    In case of error, a message is displayed, and the function
    returns exits to main menu.
   ********************************************************** */

int deleteRecord(AccountData &accHolder, const char positive,
                      const char negative, const string fileName)
{
    long recNum = 0;
    char choice = ' ';
    string tmpFile = "tmpRec.dat";

    fstream deleteRec(fileName, ios::in | ios::out | ios::binary);

    if (!deleteRec.fail())
    {
        cin.ignore();
        cout << "\nEnter Customer Account Number: ";
        cin >> recNum;

        deleteRec.seekg((recNum - 1) * sizeof(accHolder));
        deleteRec.read(reinterpret_cast<char *>(&accHolder), sizeof(accHolder));
        displayRecord(accHolder);

        cout << "Do you really wish to delete this customer account record? ";
        cin >> choice;
        cout << "\n";

        choice = toupper(choice);

        while (toupper(choice) != positive && toupper(choice) != negative)
        {
            cout << "Do you really wish to delete this customer account record? ";
            cin >> choice;
            cout << "\n";
        }

        if (toupper(choice) == positive)
        {          
            removeRec(accHolder, deleteRec, fileName, tmpFile, recNum);
        }
        else
        {
            cout << "\nChoice confirmed. No data has been changed.\n"
                  << "Returning to main menu ...\n";
        }
    }
    else
    {
        cout << "\nFILE ERROR: Data could not be read from or written to " << fileName << "\n"
              << "Now returning to main menu ...\n";
        return -1;
    }
    deleteRec.close();

    remove(fileName.c_str());
    rename(tmpFile.c_str(), fileName.c_str());

    return 0;
}

/* **********************************************************
   Definition: removeRec

    This function accepts a structure variable passed to it
    by reference, an fstream object, two file names, and the
    record number to be deleted as its arguments. First, the
    record to be eliminated is overwritten with blank data.
    It then opens a temporary file for writing. Upon success,
    all records holding an account balance greater than 0 are
    written to this temporary file, and the function returns
    to the main menu. If an error occurs, a message is output
    to screen, and the function exits to main menu.
   ********************************************************** */

int removeRec(AccountData &accHolder, fstream &deleteRec,
                  const string fileName, const string tmpFile, int recNum)
{
    AccountData cleanRec{};

    fstream tempRec(tmpFile, ios::out | ios::binary);

    deleteRec.seekp((recNum - 1) * sizeof(accHolder), ios::beg);
    deleteRec.write(reinterpret_cast<char *>(&cleanRec), sizeof(AccountData));

    /* Reset file position to 0 */
    deleteRec.clear();
    deleteRec.seekg(0L, ios::beg);

    if (!tempRec.fail())
    {
        while (deleteRec.read(reinterpret_cast<char *>(&accHolder), sizeof(accHolder)))
        {
            if (accHolder.accountBalance > 0)
            {
                tempRec.write(reinterpret_cast<const char *>(&accHolder), sizeof(accHolder));
            }
        }
        cout << "Record # " << (recNum) << " deleted.\n"
              << "Returning to main menu ...\n";
    }
    else
    {
        cout << "\nFILE ERROR: Could not create or write to " << tmpFile << "\n"
              << "Returning to main menu ...\n";
    }
    tempRec.close();

    pauseSystem();
    return 0;
}

/* **********************************************************
    Definition: displayRecord

    This function accepts a reference variable to a struct and
    a const string object as arguments. It diplays a single
    customer account record.
    ********************************************************** */

void displayRecord(AccountData &accHolder)
{
    cout << "\nAccount ID # " << accHolder.accNumber << "\n"
          << "Name: " << accHolder.name << "\n"
          << "Address: " << accHolder.address << "\n"
          << "City: " << accHolder.city << "\n"
          << "State: " << accHolder.state << "\n"
          << "Zip-Code: " << accHolder.zipCode << "\n"
          << "Telephone # " << accHolder.telephoneNumber << "\n";
    cout << fixed << showpoint << setprecision(2);
    cout << "Account Balance: JPY " << accHolder.accountBalance << "\n"
          << "Date of Last Payment: " << accHolder.dateLastPayment << "\n\n";
}             

Example Output:












Thursday, July 27, 2017

Programming Challenge 12.15 - Average Number of Words

Example File: wText.txt


/* Average Number Of Words - This program processes the contents of a text-
    file. The text is stored as one sentence per line. It reads the file's
    contents and calculates the average number of words per sentence. */

#include "Utility.h"

int  readFile();
void erasePunct(string &);
void analyzeText(const string);
void displaySentence(string);

int main()
{
    int fOpenErr = 0;

    cout << "TEXT ANALYSIS\n";

    fOpenErr = readFile();

    if (fOpenErr != -1)
    {
        cout << "Thank you for using this program, have a nice day!\n";
    }

    pauseSystem();
    return 0;
}

/* **********************************************************
    Definition: readFile

    This function attempts to open a file containing a text.
    The user is asked to enter the name of this file. Upon
    success, the file contents is read in and processed. If an
    error occurs, the function exits with an error message.
    ********************************************************** */

int readFile()
{
    string tmpString = "";
    string fileName = "";

    cout << "\nPlease enter a filename: ";
    cin >> fileName;

    cout << "\nTEXT ANALYSIS - AVERAGE NUMBER OF WORDS IN SENTENCE\n\n";

    fstream getText(fileName, ios::in);

    if (!getText.fail())
    {

        while (getline(getText, tmpString))
        {   
            displaySentence(tmpString);
            erasePunct(tmpString);
            analyzeText(tmpString);
        }

        getText.clear();
        getText.seekg(0L, ios::beg);

        cout << "\nTEXT ANALYSIS - FULL TEXT ANALYSIS\n\n";
        while (getline(getText, tmpString, '\0'))
        {
            displaySentence(tmpString);
            erasePunct(tmpString);
            analyzeText(tmpString);
        }
    }
    else
    {
        cout << "\nThis file could not be opened or processed.\n"
              << "Aborting the program now ...";
        return -1;
    }
    getText.close();

    return 0;
}

/* **********************************************************
    Definition: erasePunct

    This function accepts a reference to a string object as
    its parameter. It strips all punctuation and asterisk
    characters from the sentences. It also replaces hyphens
    with whitespace characters.
    ********************************************************** */

void erasePunct(string &sentence)
{
    for (size_t index = 0; index < sentence.length(); index++)
    {
        if (sentence[index] == '-' && isalnum(sentence[index + 1]))
        {
            sentence[index] = ' ';
        }
    }
   
    sentence.erase(remove(sentence.begin(), sentence.end(), '*'), sentence.end());
    sentence.erase(remove_if(sentence.begin(), sentence.end(), ispunct), sentence.end());
}

/* **********************************************************
    Definition: analyzeText

    This function accepts a const string object as parameter.
    It calculates the average sentence length, then displays
    the following items:

    * The number of characters in the sentence
    * The number of words in the sentence
    * The average sentence length in percent
    ********************************************************** */

void analyzeText(const string sentence)
{
    bool   isDelim = true;
    double avgNumWords = 0.0;

    size_t wordCount = 0;
    size_t numChars = 0;
    size_t wordLength = 0;

    for (size_t index = 0; index < sentence.length(); ++index)
    {
        if (isspace(sentence[index]))
        {
            ++numChars;
            isDelim = true;   
        }
        else if (isDelim)
        {
            wordLength = (sentence.length() - numChars);
            ++wordCount;
            isDelim = false;
        }
        avgNumWords = (static_cast<double>(wordLength) / wordCount);
    }

    cout << showpoint << fixed << setprecision(2);
    cout << "Characters: " << "\t" << "Word Count: " << "\t" << "Average Word Number:\n";
    cout << wordLength << " \t\t";
    cout << wordCount << " \t\t";
    cout << "%" << avgNumWords << "\t\n\n";
}

/* **********************************************************
    Definition: displaySentence

    This function accepts a string object as its parameter. It
    displays the sentences.
    ********************************************************** */

void displaySentence(string sentence)
{
    for (size_t index = 0; index < sentence.length(); index++)
    {
        if (sentence[index] == '*')
        {
            sentence[index] = '\n';
        }
    }   
    cout << sentence << "\n\n";
}

Example Output:






Monday, July 24, 2017

Programming Challenge 12.14 - Inventory Screen Report

Example File: PR_Item.dat


/* Inventory Screen Report - This program reads the data in the file created
    by the program in Programming Challenge 12.13. The program calculates and
    displays the following data:
   
        * The total wholesale value of the inventory
        * The total retail value of the inventory
        * The total quantity of all items in the inventory */

#include "Utility.h"

const int DESCR_SIZE = 25;
const int DATE_SIZE = 12;
const string ADMIN_NAME = "Administrator Rhodan";

struct Inventory
{
    int    numRecord;                       /* Holds the record number                */
    char   itemDescr[DESCR_SIZE];        /* Holds the item name                    */
    int    atHand;                            /* Quantity of items available        */
    double wholesaleCost;                /* Holds the wholesale cost            */
    double retailCost;                    /* Holds the retail cost                */
    char   dateAdded[DATE_SIZE];        /* Holds the date an item was added */

    /* Inventory Constructor */
    Inventory()
    {
        numRecord = 0;
        itemDescr[DESCR_SIZE] = ' ';
        atHand = 0;
        wholesaleCost = 0.0;
        retailCost = 0.0;
        dateAdded[DATE_SIZE] = ' ';
    }

    /* Inventory Destructor */
    ~Inventory()
    {
    }
};

struct ItemValue
{
    int    atHandTotal;        /* Holds the total amount of items available       */
    double wholesaleTotal;    /* Holds the total wholesale value of all items */
    double retailTotal;        /* Holds the total retail value of all items    */

    ItemValue()
    {
        atHandTotal = 0;
        wholesaleTotal = 0.0;
        retailTotal = 0.0;
    }   

    ~ItemValue()
    {
    }
};

int readFile(Inventory &, ItemValue &);
void calcValue(Inventory &, ItemValue &);
void showInventory(const Inventory &);
void showValue(const ItemValue &);

int main()
{
    ItemValue value;
    Inventory record;

    int fOpen = 0;

    cout << "\nCOSMIC WAREHOUSE COMPANY - TERRA HQ.\n\n"
          << "Welcome " << ADMIN_NAME << "!\n";

    fOpen = readFile(record, value);

    if (fOpen != -1)
    {
        cout << "\nI will now shut this program down, " << ADMIN_NAME << ".\n"
              << "The Cosmic Warehouse Company Item Record System "
              << "wishes you a successful day!";
    }


    pauseSystem();
    return 0;
}

/* **********************************************************
   Definition: readFile

    This function accepts two structures passed by reference
    as its arguments. The user is asked to enter the name of
    the file containing the item records. Upon success, the
    records are read in and processed. If an error occurs, the
    user is informed by a message, and the function exits.
   ********************************************************** */

int readFile(Inventory &items, ItemValue &value)
{
    string fileName = "";

    cout << "\nPlease enter the name of the file you wish me to retrieve\n"
          << "item information from, " << ADMIN_NAME << ": ";
    cin >> fileName;

    fstream readRec(fileName.c_str(), ios::in | ios::binary);

    if (!readRec.fail())
    {
        cout << "\nI successfully retrieved the data from " << fileName
              << ", " << ADMIN_NAME << "...\n"
              << "I will now process and display all available data ...\n\n";

        while (readRec.read(reinterpret_cast<char *>(&items), sizeof(items)))
        {
            calcValue(items, value);
            showInventory(items);
        }
        showValue(value);
    }
    else
    {
        cout << "\nI could not retrieve any item information from " << fileName << ".\n"
              << "\nSorry for the inconvenience, " << ADMIN_NAME << ".\n"
              << "A service technician will immediately resolve this problem.\n"
              << "The Cosmic Warehouse Company Item Record System will now shut down ...";
        return -1;
    }
    readRec.close();

    return 0;
}

/* **********************************************************
   Definition: calcValue

    This function accepts two structures passed by reference
    as its arguments. It calculates the total wholesale and
    retail values, as well as the total number of items. This
    data is stored in the appropriate member variables of the
    ItemValue structure.
   ********************************************************** */

void calcValue(Inventory &items, ItemValue &calcValue)
{
        calcValue.wholesaleTotal += items.wholesaleCost * items.atHand;
        calcValue.retailTotal     += items.retailCost * items.atHand;
        calcValue.atHandTotal     += items.atHand;
}

/* **********************************************************
   Definition: showInventory

    This function accepts a const structure variable passed to
    it by reference as its argument. It displays information
    about all items stored in the structure member variables.
   ********************************************************** */

void showInventory(const Inventory &items)
{

    cout << setw(19) << left << "Item Record Number:\t\t"
          << items.numRecord << "\n";
    cout << setw(19) << left << "Item Description:\t\t"
          << items.itemDescr << "\n";
    cout << setw(17) << left << "Quantity Available:\t\t"
          << items.atHand << "\n";
    cout << showpoint << fixed << setprecision(2);
    cout << setw(16) << left << "Wholesale Cost: "
          << setw(15) << right << "$ "
          << setw(9) << right << items.wholesaleCost << "\n";
    cout << setw(16) << left << "Retail    Cost: "
          << setw(15) << right << "$ "
         << setw(9) << right << items.retailCost << "\n";
    cout << "Date Added: " << setw(30) << right << items.dateAdded << "\n\n";
}

/* **********************************************************
   Definition: showValue

    This function accepts a const structure member variable
    passed by reference as its argument. It displays:

        * The total wholesale value of the items
        * The total retail value of the items
        * The total number of available items
   ********************************************************** */

void showValue(const ItemValue &value)
{
    cout << setw(16) << left << "Total Wholesale Value: "
          << setw(8) << right << "$ "
          << setw(4) << right << value.wholesaleTotal << "\n";
    cout << setw(16) << left << "Total Retail    Value: "
          << setw(8) << right << "$ "
          << setw(4) << right << value.retailTotal << "\n\n";
    cout << setw(19) << left << "Total Items Available: "
          << setw(12) << right << value.atHandTotal << "\n";
}

Example Output:





Bugfix Notice

It has happened again. Twice now, and in a very short period of time at that, I committed some errors in the code for Programming Challenge 12.13. Unlike the last time, I decided to edit the original code, and resubmit it, which is unfortunate. 

The nature of the error resulted from a poor design decision I originally made while in the planning stages. It was looking like a great idea to have a nested structure from which two other structures would be called. What I did not know, or hadn't realized, is the fact that you can have a nested structure, but this brings along many troubles if you're doing it wrong.

Here is part of the original code:


As you will notice there is a structure called Item, which holds two nested structure members. One calls the Date structure, the other calls the Inventory structure. In the code that follows, the Item structure was passed down to all the other functions, and this is what introduced the error. The way the original code was, I had this statement in the write function:

writeRec.write(reinterpret_cast<char *>(&itemRec), sizeof(itemRec));

This must have also written part of the Date information to the file. The same must have happened with the edit function, which contains a very similar line of code to the above. When I started writing code for the Programming Challenge 12.14, it became clear that the records haven't been read in correctly. All data was displayed in the wrong field, item description in the dateAdded field for instance. The 'easy fix' would have been to add the Date structure to the current Programming Challenges' code. With it in place, all data contained in the file was displayed correctly. 

Since it is important only in the original program, but only an artifact in the current program, some changes to the old code was the better way to handle things. A new filed was also added to the Programming Challenge 12.13 code: The 'numRecords' field, which is now contained in the Inventory structure. The idea is to have some sort of 'limit' in place, which is something that I think is beneficial to the current Programming Challenge code.
 
Unlike last time it also had a positive effect. I do know now that structures, if used in the way I did, can cause unexpected trouble. Also I learned that, if there is a Programming Challenge 1 and a Programming Challenge 2 that builds on the first code, I should try functionality in both, before submitting one Challenge, only to find that things would not work in the program that follows it.  

To my readers, if you happen to have run the original code or using the example file: PR_Item.dat, please download it again, and give it another try. Also here is the original Programming Challenge 12.13 code for review purposes. I can only promise and hope that such mistakes will not happen again. I would also like to say how sorry I am for introducing such problems. 

Now it is back to the IDE to write and finish Programming Challenge 12.14, which should go live very soon. My fellow learners I wish that they be wiser and know when and how to use structures the right way, especially when dealing with input and output to files.