8/22/2014

Inner product, round 3: handling I/O

Entering round 3, our sub-problem backlog looks like this:

P1. Prompting the user
P2. Computing the inner product. 
P3. Displaying the output.
P4. Test inner product computation in a separate project. 

P1 and P3 deals with I/O, so let's take them both in this round.

Step U. Understanding the problem.

To be useful, your program needs to make contact with the outside world. In this case, the outside world is simply the console input and the console output, supported by C++ through std::cin and std::cout, respectively.

Console I/O seems straightforward enough that people get caught unprepared. As a programmer, you need to beware of what's beyond the console? Who's typing in the input? Who's reading the output? The answer in the most straightforward sense: the user. User makes mistakes; your program needs to be tolerant. This means that your program should do numerous checks for errors committed by the user and respond properly, keeping the program alive.
 
Step D. Devising a plan.

We had our first encounter with std::cout in a typical "hello, world!" program. That was easy. But now, since the console is both the input and the output, things are a little bit more cumbersome, especially in the case of std::cin. So, instead of working closely with std::cin on an item-by-item basis, e.g., reading the components of a vector one at a time, we are going to take the following strategy: get the one vector into a string in one read, and extract the vector from that string. Strings are easy to work with because it is now a variable inside your program, and C++ provides a powerful string object with many routines for manipulations. So we have a decomposition into tasks like

T1. Get user input into a string.
T2. Extract vector from the string.
T3. Test the function written in T2.
T4. Display output to cout.

Note that I have added T3, a task for unit testing. While unit test should accompany any unit of code, people tend to easily forget. The task serves as a reminder. This is simple strategy called "Be specific until it's a habit."

Step C. Carrying out the plan.

T3. Test the function written in T2. In the two tests below, the string s is the string for specifying the vector. For example, the string "2 [1,2]" stands for a 2-dimensional vector [1,1].

TEST (extractVectorFromString, OK)
{
    string s("2 [1,1]");
    int d;
    double * vec;
    vec = extractVectorFromString(s, d);
    LONGS_EQUAL(2, d);
    DOUBLES_EQUAL(1.0, vec[0], 0.0001);
    DOUBLES_EQUAL(1.0, vec[1], 0.0001);
}


TEST (extractVectorFromString, DimErrorUnder)
{
    string s("1 [1,1]");
    int d;
    try {
        extractVectorFromString(s, d);
        CHECK(false);
    }
    catch (char const * s) {
        CHECK(0==strcmp("Dimension error!",s));
    }
}
 

 
There is one more point to T3. Our capability to test the user input under cppunitlite without breaking its promise to be "silent unless broken" depends on getting the inputs into strings. Strings are easy to work with in tests, but std::cin isn't. In the code above, the first test gets a string "2 [1,1]", reflecting the case where the user has, correctly, input a two dimensional vector [1,1]. In the second test simulate the case where the user types in "1 [1,1]", a one dimensional vector [1,1], which is obviously is error. Note that the function
extractVectorFromString() throws an exception after detecting this error. 

Here is the working program
 
Step L. Looking back. 

In this round, we solved two sub-problems due to to their similarity in nature. Also, looking back at the Step D,  you can see how your knowledge of unit testing changes the way partition a sub-problem into tasks. There is no longer throw-away test code that borrows space from the main function; test code now takes residence and accumulates in the test project. In fact, you can go one step further to practice what is called Test-driven development: write up the test code, then write the production code. We will have more to say about this later.  

Updating the sub-problem backlog, it is found that all sub-problems are solved:

P1. Prompting the user
P2. Computing the inner product. 
P3. Displaying the output.
P4. Test inner product computation in a separate project. 

With that, we have solved the problem of computing inner product. The program is mainly in C with some use of C++ objects from the standard library; exception handling is used to deal with errors; and unit tests were written for the program. So, if you run out of development time or the client is in a hurry to release, the program is good to go! 

But since we are doing object-oriented development, we will try to make our vectors object-oriented -- for what? It's time to look back again as a programmer and hunt for improvement opportunities.


Asking questions at the right time help one learn and improve. In How To Solve It, one of the question Pólya asks in Looking Back is this: Can you see it at a glance? Let's ask this question: As a programmer, can you or someone else easily understand what your program does?

We have the following two specific questions:

Can you see that your program is dealing with vectors and computation of inner products?

We can do this by reviewing the code. Let's do this for the the function computerInnerProduct. 



With the exception of lines 4 and 7, and probably the name of the variables that start with the letter v, nothing much of the function is about vectors! As it stands, the code has more "array-ness" than "vector-ness" to it. Further, if you ask anyone who knows vectors in mathematics, they will probably have trouble understanding the for-loop because a vectors starts with dimension 1 rather than dimension 0. 

If you use the functions of this program in the future, what mistakes are you most likely make?

In calling the function computeInnerProduct with vector v1 of dimensional d1 and vector v2 of dimension d2, someone could write

    double r = compuetInnerProduct(v1, v2, d1, d1);

Can you see what is wrong at a glance ? If yes, good for you! But now try this:

   double r = compuetInnerProduct(v2, v1, d1, d2);

Can you still see it at a glance? The trouble with representing a vector with an array of doubles is that the dimension is kept away from the vector. You know that they are tightly related only because you made the cognitive effort to remember this relationship. But the trouble is deeper than you think. If fact, in the array notation, it is easy for someone to write v[2] for getting the component at the second dimension when she/he should write v[1] instead. Having a v[2] instead of v[1] means there is a bug in code, and such a bug is difficult to catch.   
    
Let's pose a new problem in the backlog. 

P5. Representation of vectors: Vector should remember its own dimension; use of vector should follow the convention in mathematics. 

© Y C Cheng, 2013, 2014. All rights reserved.

No comments:

Post a Comment