Accelerated C++ Solution to Exercise 4-3

Exercise 4-3

What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

Solution

Though there are many ways to solve this problem, below is the solution strategy that I use, with the aim of enhanced flexibility:

  1. Obtain the asymmetric range [m,n) from the user with the condition of n > m. In other words, m is the startNumber. n – 1 is the endNumber.
  2. Compute the number of elements within this asymmetric range as n – m. This is equivalent to the number of loops required (or number of rows to output).
  3. Create a function getStreamWidth that will be used to automatically compute the maximum width required for column 1 (the list of numbers), and column 2 (the square of column 1). This function is capable of dealing with both negative and non-negative input values.
  4. Have a for loop to output column 1 and 2 using the corresponding column stream widths computed upfront (step 3 above).

The Project

I have decided to partition the program as follows – for practice sake.

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • getStreamWidth.cpp – contains all functions relating to obtaining the stream widths.

C++ Header Files

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp4p3MgntTree

The actual content of the source and header files are documented in the following sections.

Source Files

main.cpp

#include <iostream>
#include <ios>
#include <iomanip>
#include <algorithm>
#include "getStreamWidth.h"

using std::cout;  // <iostream>
using std::cin;   // <iostream>
using std::endl;  // <iostream>
using std::streamsize;  // <ios>
using std::setw;  // <iomanip>
using std::max;  // <algorithm>

int main()
{
#include <iostream>
#include <ios>
#include <iomanip>
#include <algorithm>
#include "getStreamWidth.h"

using std::cout;  // <iostream>
using std::cin;   // <iostream>
using std::endl;  // <iostream>
using std::streamsize;  // <ios>
using std::setw;  // <iomanip>
using std::max;  // <algorithm>

int main()
{
    // display program intro message
    cout << "***********************************************************\n"
         << "*** This program computes the square of the numbers     ***\n"
         << "*** in the asymmetric range [m,n).                      ***\n"
         << "*** (Limitation: please ensure n > m)                   ***\n"
         << "*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***\n"
         << "***********************************************************";
    cout << endl;

    // ask user to supply m
    cout << "Enter m: ";
    int m;
    cin >> m;

    // ask user to supply n
    cout << "Enter n: ";
    int n;
    cin >> n;

    // ensure m and n are input correctly. If not, exit program.
    if (n <= m)
    {
        cout << "Please make sure n > m";
        return 1;
    }

    // initialise value
    const int startNumber = m;    // first output integer
    const int endNumber = n - 1;  // last output integer
    const int numLoops = n - m;  // number of rows to output

    // find the maxwidth for column 1 and 2
    const streamsize col1Width = max(getStreamWidth(startNumber), getStreamWidth(endNumber));
    const streamsize col2Width = max(getStreamWidth(startNumber * startNumber), getStreamWidth(endNumber * endNumber));

    // display a summary
    cout << "Asymmetric range: [" << m << "," << n << ")" << endl;
    cout << "Number of rows = " << numLoops << endl;
    cout << "Column 1 width = " << col1Width << " | Column 2 width = " << col2Width << endl;

    // get ready to print report
    int y = startNumber;
    for (int i = 0; i != numLoops; ++i)
    {
        cout << setw(col1Width) << y << setw(col2Width) << (y * y) << setw(0) << endl;
        ++y;
    }
  return 0;
}

getStreamWidth.cpp

#include <ios>

using std::streamsize;

// return the required streamsize to fit a particular integer number
streamsize getStreamWidth(int number)
{

    streamsize numDigits;

    // initialise numDigits and number depending on whether value is positive / negative.
    // If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
    // If positive, require at least 1 space to fit the leading empty space string
    if (number < 0)
    {
        numDigits = 2;
        number *= -1;
    }
    else numDigits = 1;

    // numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
    // i.e. this is equivalent to the total stream width required
    while (number != 0)
    {
        ++numDigits;
        number /= 10;
    }

    return numDigits;
}

Header Files

getStreamWidth.h

#ifndef GUARD_GETSTREAMWIDTH_H
#define GUARD_GETSTREAMWIDTH_H

std::streamsize getStreamWidth(int number);

#endif // GUARD_GETSTREAMWIDTH_H

Test Results

Asymmetric range [-3, 4)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: -3
Enter n: 4
Asymmetric range: [-3,4)
Number of rows = 7
Column 1 width = 3 | Column 2 width = 2
 -3 9
 -2 4
 -1 1
  0 0
  1 1
  2 4
  3 9

Asymmetric range [995,1006)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: 995
Enter n: 1006
Asymmetric range: [995,1006)
Number of rows = 11
Column 1 width = 5 | Column 2 width = 8
  995  990025
  996  992016
  997  994009
  998  996004
  999  998001
 1000 1000000
 1001 1002001
 1002 1004004
 1003 1006009
 1004 1008016
 1005 1010025

Asymmetric range [-1005,-996)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: -1005
Enter n: -996
Asymmetric range: [-1005,-996)
Number of rows = 9
Column 1 width = 6 | Column 2 width = 8
 -1005 1010025
 -1004 1008016
 -1003 1006009
 -1002 1004004
 -1001 1002001
 -1000 1000000
  -999  998001
  -998  996004
  -997  994009

Asymmetric range [0,1000)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: 0
Enter n: 1000
Asymmetric range: [0,1000
Number of rows = 1000
Column 1 width = 4  Column 2 width = 7
   0      0
   1      1
   2      4
   ...
 993 986049
 994 988036
 995 990025
 996 992016
 997 994009
 998 996004
 999 998001

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-2

Exercise 4-2

Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value. Use setw to manage the output so that the values line up in columns.

Solution

To keep the code as simple as possible, for the sake of solving this particular problem, I will have some kind of hard-code values in the codes. Of course, the code can be made more intelligent later on and remove the need of hard-coding!

The setw(n) function returns a value of type streamsize that, when written on an output stream s, has the effect of calling s.width(n).

After doing some experiments in Code::Block I learn that, if (say) the integer width is smaller than n, the outputstream will simply align the numbers to the right, and pad the left side with leading empty spaces. Likewise for strings.

We are asked to output two columns.

Column 1 contains a list of integers defined by this symmetric range [0,100] – i.e. between 0 and 100 inclusively. We therefore require n = 3 for this, so we can fit in the max integer 100.

Column 2 contains a list of integers defined by this symmetric range [0, 10000] – i.e. between 0 and 10000 inclusively. We therefore require n = 6 for this, so we can fit in the max integer 10000 (which takes up a width of 5), plus 1 leading empty space to separate this column from column 1.

The program will look like this:

#include <iostream>
#include <iomanip>

using std::cout;
using std::endl;
using std::setw;

int main()
{
    for (int i = 0; i != 101; ++i)
    {
        cout << setw(3) << i << setw(6) << (i * i) << endl;
    }
}

Result

  0     0
  1     1
  2     4
  3     9
  4    16
  5    25
  6    36
  7    49
  8    64
  9    81
 10   100
 11   121
 12   144
 13   169
 14   196
 15   225
 16   256
 17   289
 18   324
 19   361
 20   400
 21   441
 22   484
 23   529
 24   576
 25   625
 26   676
 27   729
 28   784
 29   841
 30   900
 31   961
 32  1024
 33  1089
 34  1156
 35  1225
 36  1296
 37  1369
 38  1444
 39  1521
 40  1600
 41  1681
 42  1764
 43  1849
 44  1936
 45  2025
 46  2116
 47  2209
 48  2304
 49  2401
 50  2500
 51  2601
 52  2704
 53  2809
 54  2916
 55  3025
 56  3136
 57  3249
 58  3364
 59  3481
 60  3600
 61  3721
 62  3844
 63  3969
 64  4096
 65  4225
 66  4356
 67  4489
 68  4624
 69  4761
 70  4900
 71  5041
 72  5184
 73  5329
 74  5476
 75  5625
 76  5776
 77  5929
 78  6084
 79  6241
 80  6400
 81  6561
 82  6724
 83  6889
 84  7056
 85  7225
 86  7396
 87  7569
 88  7744
 89  7921
 90  8100
 91  8281
 92  8464
 93  8649
 94  8836
 95  9025
 96  9216
 97  9409
 98  9604
 99  9801
100 10000

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-1

Exercise 4-1

We noted in S4.2.3/65 that it is essential that the argument types in a call to max match exactly. Will the following code work? If there is a problem, how would you fix it?

int maxlen;
Student_info s;
max(s.name.size(), maxlen);

Solution

If we refer back to the program in S4.2.3/65, s.name.size() is of type std::string::size_type. If we call the std::max function with argument 1 (s.name.size()) that is of std::string::size_type, and argument 2 (maxlen) that is of type int, we are essentially comparing variables of different types. We are “comparing apple with orange”! The program will bump into compilation failure and complain.

To amend this, just simply make maxlen the same type as s.name.size(). We assume at the top of the program we already have all the required using std::xxx declarations.

string::size_type maxlen;
Student_info s;
max(s.name.size(), maxlen);

Now, both arguments 1 and 2 that we supply to the max function are of the same type string::size_type. i.e. We are now “comparing apple with apple”. The program should now compile.

Proof of Concept

Seeing is believing. I now provide the tangible evidence of why the above debate is so. To do this, I create a very simple program that simulates that “invalid” code in question, then compile it, then see compilation error, then go and fix it, then re-compile it, and demonstrate no compilation error after fixing.

Before the Fix

For instance, I explicitly specify the Student_info object type here.

#include <iostream>
#include <string>
#include <algorithm>

struct Student_info
{
    std::string name;
};

int main()
{
    int maxlen;       // this causes error
    Student_info s;
    std::max(s.name.size(), maxlen);
    return 0;
}

Submitting this program in an IDE (e.g. Code::Block) for compilation return the following errors:

error: no matching function for call to ‘max(std::basic_string<char>::size_type, int&)’

The compiler detects that we are comparing two different types and complain accordingly.

After the Fix

We can correct this easily by changing the int maxlen to std::string::size_type maxlen. i.e. corrected program as following:

#include <iostream>
#include <string>
#include <algorithm>

struct Student_info
{
    std::string name;
};

int main()
{
    std::string::size_type maxlen;   // this works
    Student_info s;
    std::max(s.name.size(), maxlen);
    return 0;
}

Submitting this corrected program the IDE now compiles smoothly.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000