Portability
#import
is a GCC extension (or perhaps a preview of C++20).
There's no good reason not to simply #include <cmath>
here.
Headers and namespaces
We don't use any string-stream, so #include <sstream>
can be removed.
Bringing all names in from a namespace is problematic; namespace std
particularly so. It can silently change the meaning of your program when you're not expecting it. Get used to using the namespace prefix (std
is intentionally very short), or importing just the names you need into the smallest reasonable scope.
In this program, the only places the std::
prefix were missing were std::ifstream
and std::sqrt
, so this wasn't hard to fix.
readFile()
These lines can be simplified:
std::ifstream in_file;
in_file.open(filePath);
We can ask the constructor to open the file for us:
std::ifstream in_file(filePath);
This loop has some error checking, but it's not complete:
while(std::getline(in_file,line,'\r')){
numbers.push_back(std::stoi(line));
}
Firstly, we don't expect any carriage-return in the input file (we opened it in text mode, so on systems that use CR as line delimiter, they will be converted to \n
). Secondly, std::stoi
throws exceptions when the string cannot be converted, but we probably also want to check whether there are leftover, unconverted characters after our integer (e.g. if someone thought they could supply decimal values).
computeMean()
Why return a float
rather than double
? Single-precision floats are normally used only where the storage size is an important consideration, which is not the case here. (Note that on many platforms, double
is the natural (and fastest) size of floating-point.)
We should pass the vector by reference to a const object, as we don't want to make a copy or to modify the value.
Instead of returning zero when there are no members, perhaps we should return a NaN value (which is more consistent with arithmetic 0.0 / 0
):
if (numbers.empty())
return std::numeric_limits<double>::quiet_NaN();
This loop:
double total = 0;
for (int number : numbers) {
total += number;
}
can be written (with #include <numeric>
) as
double total = std::accumulate(numbers.begin(), numbers.end(), 0.0);
computeVariance()
We need to be clear which variance (sample or population) we're returning. We're also missing a size check similar to that for the mean.
Apart from that, the comments above for computeMean()
are relevant:
double computeSampleVariance(const double mean, const std::vector<int>& numbers)
{
if (numbers.size() <= 1u)
return std::numeric_limits<double>::quiet_NaN();
auto add_square = [mean](double sum, int i)
{
auto d = i - mean;
return sum + d*d;
};
double total = std::accumulate(numbers.begin(), numbers.end(), 0.0, add_square);
return total / (numbers.size() - 1);
}
Single-pass algorithm
It is possible to compute the mean and variances in a single pass - but not using the (exact-arithmetic) methods you likely learnt in high school, which suffer from lack of precision with the inexact floating-point types we can use. The topic is too deep for this review, but if you research Welford's Algorithm, you will find reference implementations to guide you.
That said, for your purposes, the straightforward two-pass algorithm is probably appropriate, and it's easy to read and understand, so I wouldn't recommend changing it unless you reach a point where your input set becomes too large to hold in a vector (and even then, only if you can't read the file multiple times).
My version
#include <algorithm>
#include <cmath>
#include <fstream>
#include <iostream>
#include <iterator>
#include <limits>
#include <numeric>
#include <vector>
std::vector<int> readFile(const std::string& filePath)
{
std::ifstream in_file(filePath);
std::istream_iterator<int> start{in_file}, end;
std::vector<int> numbers;
std::copy(start, end, std::back_inserter(numbers));
return numbers;
}
double computeMean(const std::vector<int>& numbers)
{
if (numbers.empty())
return std::numeric_limits<double>::quiet_NaN();
return std::accumulate(numbers.begin(), numbers.end(), 0.0) / numbers.size();
}
double computeSampleVariance(const double mean, const std::vector<int>& numbers)
{
if (numbers.size() <= 1u)
return std::numeric_limits<double>::quiet_NaN();
auto const add_square = [mean](double sum, int i) {
auto d = i - mean;
return sum + d*d;
};
double total = std::accumulate(numbers.begin(), numbers.end(), 0.0, add_square);
return total / (numbers.size() - 1);
}
int main()
{
#ifdef TEST
const std::vector<int> numbers = { -2, -1, 1, 2, 100000-2, 100000-1, 100000+1, 100000+2};
#else
std::cout << "Please enter the file path :" << std::endl;
std::string filePath;
std::cin >> filePath;
const std::vector<int> numbers = readFile(filePath);
#endif
double mean = computeMean(numbers);
double variance = computeSampleVariance(mean, numbers);
double standardDeviation = std::sqrt(variance);
std::cout << numbers.size() << " numbers : ";
auto separator = "";
for (int number: numbers) {
std::cout << separator << number;
separator = ", ";
}
std::cout << std::endl;
std::cout << "Mean : " << std::to_string(mean)
<< "Variance : " << std::to_string(variance)
<< "Standard Deviation : " << std::to_string(standardDeviation) << std::endl;
return 0;
}