I find that I often want to calculate summary statistics from collections of data and decided to create a template to do that. I'm interested in a general review, but I also have some specific questions.
- Is the use of the lambda "too clever?"
- Is
std::accumulate
OK? Specifically, I'm thinking of the potential for overflow for large collections of small data types. - Is the template missing anything?
- Should I be using something such as
std::enable_if
to assure that theIter
type is actually an iterator?
The code to be reviewed is in stats.h
but stats.cpp
shows how it can be used.
stats.h
#ifndef STATS_H
#define STATS_H
#include <cmath>
#include <numeric>
#include <iterator>
template <typename T, typename Iter>
struct Statistics {
Statistics(Iter start, Iter end, bool sample=true) :
n(std::distance(start, end)),
sum(std::accumulate(start, end, 0)),
mean(sum / n),
variance([&start, &end, sample, this]()->T {
T sumsq = 0;
for (auto it=start; it != end; ++it) {
sumsq += (*it-mean)*(*it-mean);
}
return sumsq/(n- (sample ? 1 : 0));
}()
),
sdev(sqrt(variance))
{
}
unsigned n;
T sum;
T mean;
T variance;
T sdev;
};
#endif //STATS_H
stats.cpp
#include <iostream>
#include <vector>
#include <list>
#include "stats.h"
template <typename T>
std::ostream &print(std::ostream &out, const T &stats) {
out << "n = " << stats.n << '\n';
out << "sum = " << stats.sum << '\n';
out << "mean = " << stats.mean << '\n';
out << "var = " << stats.variance << '\n';
out << "sdev = " << stats.sdev << '\n';
return out;
}
int main()
{
std::vector<int> sample{9, 2, 5, 4, 12, 7};
Statistics<float, decltype(sample.cbegin())> stats(sample.cbegin(), sample.cend());
print(std::cout, stats);
std::list<float> sample2{2.0, 4.0, 5.0, 7.0, 9.0, 12.0};
Statistics<double, decltype(sample2.cbegin())> stats2(sample2.cbegin(), sample2.cend());
print(std::cout, stats2);
}
Sample output
n = 6
sum = 39
mean = 6.5
var = 13.1
sdev = 3.61939
n = 6
sum = 39
mean = 6.5
var = 13.1
sdev = 3.61939