Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Averaging...the easy way

4.78/5 (3 votes)
31 Dec 2008CPOL4 min read 23.2K   119  
How to find the average of a set of numbers.

Introduction

Back when I was in college, one of our first (C) programming assignments was to average a set of numbers read from a file. I thought this was so cool being able to get data in this fashion. Let's look at some implementations of how to go about averaging a set of numbers from an input file.

Good

Being on a Unix box at the time, with what seemed like unlimited disk space, meant we could create humungous files as input to our code. I don't remember the specs of these machines, but even an input file in the MB range could be run through in a matter of seconds. When your only other experience was with a Commodore portable computer, even 10 minutes would have been seen as a breakthrough!

During that exercise, I learned that the first argument to fscanf() was a handle to an open file. I also found this thing called stdin that was a handle to the standard input stream, which was already open. Putting fscanf() together with the standard input stream produced a program that looked like:

C++
int    nCount = 0,
       nNumber;
double dSum = 0.0;

while (fwscanf_s(stdin, _T("%d"), &nNumber) != EOF)
{
    dSum += nNumber;
    nCount++;
}

wprintf(_T("Average = %f\n"), dSum / nCount);

Most anyone that is familiar with DOS or Unix knows how to redirect the standard input stream. From a command prompt, two such ways are:

type input_file | program.exe

and:

program.exe < input_file

You could also just type the name of the program itself and enter the data via the keyboard, but where's the fun in that? This seems simple enough, but that fwscanf_s() thing could get you into trouble if not used properly. I wonder if C++ has anything useful to offer...

Better

While I've been using MFC since its inception, I really did myself an injustice by not having studied C++ early on. It wasn't until a few years ago that I decided to start studying more of the basics of C++ along with the STL. It's amazing how many little "tools" and algorithms are available.

C++
ifstream fin;
vector<int> vNumbers;

fin.open(__wargv[1]);

copy(istream_iterator<int>(fin), istream_iterator<int>(), 
     back_inserter(vNumbers));

fin.close();

int nSum = accumulate(vNumbers.begin(), vNumbers.end(), 0);
    
wcout << (double) nSum / vNumbers.size();

To use this version, from a command prompt, type:

program.exe input_file

This was a welcome improvement over the first option. While the copy() function handles reading from the input file and storing everything in a nice little container, the accumulate() function appears to do a good job at summing everything up in that container. There still seems to be something missing though...

Best

At this point in the age of personal computers and the Windows OS, most everyone has used Microsoft Excel, or at least knows what it is. For the Rip Van Winkles in the crowd, Excel is a powerful tool you can use to create and format spreadsheets, and analyze and share information. In other words, it's a very powerful number cruncher. With all of that sophistication, surely there must be something in there that can effortlessly average a set of numbers.

Excel exposes a COM interface that we can take advantage of. The first thing we must do is to get access to an actual sheet that we can plug numbers and a formula into. To use this COM interface, we need to import information from Excel's type library. This is done via the #import directive. Note, the library ID is that of Excel v12 (2007).

C++
#import "libid:00020813-0000-0000-C000-000000000046" 
        version("1.6") 
        lcid("0") 
        auto_search 
        no_dual_interfaces 
        raw_dispinterfaces 
        auto_rename

At this point, an Excel namespace has been created that we can use to qualify the scope of each of the variables.

C++
Excel::_ApplicationPtr app;

HRESULT hr = app.CreateInstance(_T("Excel.Application"));
if (SUCCEEDED(hr))
{
    Excel::WorkbooksPtr books = app->GetWorkbooks();

    Excel::_WorkbookPtr book = books->Add();

    Excel::SheetsPtr sheets = book->GetWorksheets();

    // the argument to GetItem() is the 1-based sheet number
    Excel::_WorksheetPtr sheet = sheets->GetItem(COleVariant(1L));
}

Now that we have access to an actual sheet, we can read the numbers from the input file (__wargv[1]) and plug them into the cells of the sheet. Note that column A is used exclusively.

C++
CStdioFile file;
if (file.Open(__wargv[1], CFile::modeRead))
{
    int     nRow = 1;
    CString str,
            strLine;

    while (file.ReadString(strLine))
    {
        // ranges are expected to be in the A1 format rather than the R1C1 format
        str.Format(_T("A%d"), nRow++);
        range = sheet->GetRange(COleVariant(str), vtOptional);

        range->PutValue2(COleVariant(strLine));
    }

    file.Close();
}

As all the numbers are now plugged into the cells, we need to insert a function to average them. Where it goes is irrelevant (as long as the references are correct), but for clarity, we'll add it just below the last number.

C++
str.Format(_T("A%d"), nRow);
range = sheet->GetRange(COleVariant(str), vtOptional);

str.Format(_T("=AVERAGE(A1:A%d)"), nRow - 1);
range->PutFormula(COleVariant(str));

The last order of business is to get the result that Excel calculated for us and print it to the screen.

C++
COleVariant val = range->GetValue(COleVariant(10L));
wcout << _T("Average = ") << val.dblVal;

Wow, finally something easy and useful! I only wish I had access to this type of functionality back in college. Imagine the operations that I could have performed on that set of numbers, like summing, finding the min and max values, calculating the mode and median, etc. Excel will even let you sort a range of numbers. ::wink::

Enjoy!

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)