for_each and functors

For some reasons, I had to write a test function to measure if the distribution of the rand() function is "flat" enough. The requirement for the test is that a number (ITERATIONS) of values have to be generated in the interval [0, ELEMENTS) and that we should ensure that all the possible elements are generated a number of times in a given interval (EXP_MIN, EXP_MAX).

The test function should return a boolean stating if it succeeded or failed, and a short message should be put on the standard console output in case a generated value is external to the expected interval, stating the value under or over represented, and the actual number of elements generated.

The idea is to use a vector of ELEMENTS elements, initialized to 0, then increasing the specific item when such a value is generated by rand(). Then using a functor to evaluate if the condition is respected for each element in the vector.

Here is the code for the functor:
class ValueTester
{
private:
bool success_;
int index_;

public:
ValueTester() : success_(true), index_(0) {}
bool succeeded() { return success_; }

void operator()(int value)
{
if(value < EXP_MIN || value > EXP_MAX)
{
success_ = false;
cout << "Bad random value [" << index_ << " = " << value << ']' << endl;
}

++index_;
};
};

The class should look quite intuitive. The constructor set its initial status, the operator() gets the value for the current element and check it. The member value index_ keeps track of the current iteration on the functor so, in case of error, we can display this information with the unexpected value to the user.

The test function is this one:
bool TestRand::testValue()
{
std::vector<int> vec(ELEMENTS, 0);

for(int i = 0; i < ITERATIONS; ++i)
{
int value = static_cast<int>((static_cast<double>(rand())/(RAND_MAX + 1)) * ELEMENTS); // 1.
++vec[value];
}
return for_each(vec.begin(), vec.end(), ValueTester()).succeeded(); // 2.
}

And maybe it requires a bit more of discussion.
1. rand() generates a pseudo-random number in the interval [0, RAND_MAX], casting it do double and dividing it by RAND_MAX + 1, we ensure we have a number in [0, 1). We multiply this for ELEMENTS and cast the result to integer, to get our value in [0, ELEMENTS).
Then we use this value as index in our vector, and increase the associated element.
2. In this compact line we instantiate an object of our functor, and use it in a for_each that works on all the elements in our vector; the for_each function returns the functor used internally, so we call on it the succeeded() function that returns its status, and return it to the caller.

Notice that for_each makes a copy of the third parameter, do not accept it by reference. That means it is not a good idea to make to write the last line of testValue() in this more explicit (but wrong) way:
ValueTester vt;
for_each(vec.begin(), vec.end(), vt);
return vt.succeeded(); // ERROR: this is always true!

The ValueTester object used internally by for_each is a copy of vt, the original vt is not modified by the for_each's execution so, in this doomed version, the caller would never be acknowledged of a failure - even though the user could see with his eyes the log for the failing elements.

No comments:

Post a Comment