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:
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.
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).
#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.
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();
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.
CStdioFile file;
if (file.Open(__wargv[1], CFile::modeRead))
{
int nRow = 1;
CString str,
strLine;
while (file.ReadString(strLine))
{
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.
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.
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