First impressions
#First impressions II enjoyed reading this code. It's logically laid out (even the includes are alphabetical, so I can check at a glance), and I never felt like I was lost in the code.
Separate the argument handling from the real action
#Separate the argument handling from the real action
ThisThis program has (arguably) reached the size where doing everything in main()
is starting to become a burden. It's probably time to separate out the reading of arguments into its own function. A preparatory step is to define a type to hold the program arguments; this is specific to this program and not something to be re-used, so let's just call it program_args
:
We don't need a separate list of repeated values
#We don't need a separate list of repeated values
TheThe vector repeated
is either empty (if norepeat
is unset) or an exact copy of generated
. So we can just use the generated
list for remembering previous values. If we need more efficiency when producing large quantities of output, we could maintain a copy, but as a std::set
rather than a std::vector
.
Should filtered-out values contribute to the count?
#Should filtered-out values contribute to the count?
OurOur main loop for (long long i = 1; i <= args.number; i++)
will increment i
unconditionally, but I believe the user expects to get number
results, rather than some amount up to number
. Consider instead looping until we have number
results:
Use the standard algorithms
#Use the standard algorithms
WeWe can total the elements of a vector with std::accumulate()
:
Should fx_vect
use ld_prec
?
#Should fx_vect
use ld_prec
?
It'sIt's not obvious to a user that --suffix
filters using the full precision of the value, rather than the user's output precision. This seems like an oversight to me.
Extract the selection of good or bad random source
#Extract the selection of good or bad random source
AsAs it is, the bad_random
logic is tested outside the loop and at every iteration. We can abstract that into a function that returns our random source wrapped in a std::function
:
My version:
#My version:
ThisThis incorporates some additional small changes not otherwise mentioned, such as allowing po::value()
to deduce its template type.
Further suggestions
#Further suggestions IfIf we are testing prefix and suffix, we stringify the value for both tests - perhaps we can do that only once?
#First impressions I enjoyed reading this code. It's logically laid out (even the includes are alphabetical, so I can check at a glance), and I never felt like I was lost in the code.
#Separate the argument handling from the real action
This program has (arguably) reached the size where doing everything in main()
is starting to become a burden. It's probably time to separate out the reading of arguments into its own function. A preparatory step is to define a type to hold the program arguments; this is specific to this program and not something to be re-used, so let's just call it program_args
:
#We don't need a separate list of repeated values
The vector repeated
is either empty (if norepeat
is unset) or an exact copy of generated
. So we can just use the generated
list for remembering previous values. If we need more efficiency when producing large quantities of output, we could maintain a copy, but as a std::set
rather than a std::vector
.
#Should filtered-out values contribute to the count?
Our main loop for (long long i = 1; i <= args.number; i++)
will increment i
unconditionally, but I believe the user expects to get number
results, rather than some amount up to number
. Consider instead looping until we have number
results:
#Use the standard algorithms
We can total the elements of a vector with std::accumulate()
:
#Should fx_vect
use ld_prec
?
It's not obvious to a user that --suffix
filters using the full precision of the value, rather than the user's output precision. This seems like an oversight to me.
#Extract the selection of good or bad random source
As it is, the bad_random
logic is tested outside the loop and at every iteration. We can abstract that into a function that returns our random source wrapped in a std::function
:
#My version:
This incorporates some additional small changes not otherwise mentioned, such as allowing po::value()
to deduce its template type.
#Further suggestions If we are testing prefix and suffix, we stringify the value for both tests - perhaps we can do that only once?
First impressions
I enjoyed reading this code. It's logically laid out (even the includes are alphabetical, so I can check at a glance), and I never felt like I was lost in the code.
Separate the argument handling from the real action
This program has (arguably) reached the size where doing everything in main()
is starting to become a burden. It's probably time to separate out the reading of arguments into its own function. A preparatory step is to define a type to hold the program arguments; this is specific to this program and not something to be re-used, so let's just call it program_args
:
We don't need a separate list of repeated values
The vector repeated
is either empty (if norepeat
is unset) or an exact copy of generated
. So we can just use the generated
list for remembering previous values. If we need more efficiency when producing large quantities of output, we could maintain a copy, but as a std::set
rather than a std::vector
.
Should filtered-out values contribute to the count?
Our main loop for (long long i = 1; i <= args.number; i++)
will increment i
unconditionally, but I believe the user expects to get number
results, rather than some amount up to number
. Consider instead looping until we have number
results:
Use the standard algorithms
We can total the elements of a vector with std::accumulate()
:
Should fx_vect
use ld_prec
?
It's not obvious to a user that --suffix
filters using the full precision of the value, rather than the user's output precision. This seems like an oversight to me.
Extract the selection of good or bad random source
As it is, the bad_random
logic is tested outside the loop and at every iteration. We can abstract that into a function that returns our random source wrapped in a std::function
:
My version:
This incorporates some additional small changes not otherwise mentioned, such as allowing po::value()
to deduce its template type.
Further suggestions
If we are testing prefix and suffix, we stringify the value for both tests - perhaps we can do that only once?
- 88k
- 14
- 104
- 325
#Extract the selection of good or bad random source
As it is, the bad_random
logic is tested outside the loop and at every iteration. We can abstract that into a function that returns our random source wrapped in a std::function
:
std::function<long double()>make_random_source(const program_args& args)
{
if (args.bad_random) {
std::srand(std::time(nullptr));
const auto min = args.lbound;
const auto scale = (args.ubound - args.lbound) / RAND_MAX;
return [min,scale]{ return min + (std::rand() * scale); };
} else {
std::mt19937 generator{(std::random_device())()};
std::uniform_real_distribution<long double> dis{args.lbound, args.ubound};
return [dis,generator]() mutable -> auto { return dis(generator); };
}
}
Then we only need to obtain it (before the loop):
auto const random = make_random_source(args);
and use it (inside the loop):
long double rand = random();
#include <algorithm>
#include <boost/program_options.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <vector>
enum returnID {
success = 0,
known_err = 1,
other_err = 2,
zero_err = 3,
conflict_err = 4,
overd_err = 5,
underd_err = 6,
exclude_err = 7,
success_help = -1,
};
bool filter(const long double rand,
int precision,
const std::vector<std::string>& fx,
bool(*predicate)(const std::string&, const std::string&)) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << rand;
auto const str_rand = oss.str();
return std::none_of(fx.begin(), fx.end(), [&](auto const& s) { return predicate(str_rand, s); });
}
struct program_args {
unsigned long long number;
long double lbound, ubound;
bool ceil, floor, round, trunc; // mutually exclusive
int precision;
std::vector<long double> excluded;
bool norepeat, stat_min, stat_max, stat_median, stat_avg, bad_random, list, quiet;
std::vector<std::string> prefix, suffix, contains;
std::string delim = "\n";
};
returnID parse_args(program_args &args, int argc, char const *const *argv)
{
static auto const ld_prec = std::numeric_limits<long double>::max_digits10;
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("help,h", "produce this help message")
("number,n", po::value(&args.number)->default_value(1),
"count of numbers to be generated")
("lbound,l", po::value(&args.lbound)->default_value(0.0),
"minimum number(ldouble) to be generated")
("ubound,u", po::value(&args.ubound)->default_value(1.0),
"maximum number(ldouble) to be generated")
("ceil,c", po::bool_switch(&args.ceil)->default_value(false),
"apply ceiling function to numbers")
("floor,f", po::bool_switch(&args.floor)->default_value(false),
"apply floor function to numbers")
("round,r", po::bool_switch(&args.round)->default_value(false),
"apply round function to numbers")
("trunc,t", po::bool_switch(&args.trunc)->default_value(false),
"apply truncation to numbers")
("precision,p", po::value(&args.precision)->default_value(ld_prec),
"output precision(not internal precision, cannot be > ldouble precision)")
("exclude,e", po::value(&args.excluded)->multitoken(),
"exclude numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("norepeat,x", po::bool_switch(&args.norepeat)->default_value(false),
"exclude repeated numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("stat-min", po::bool_switch(&args.stat_min)->default_value(false),
"print the lowest value generated")
("stat-max", po::bool_switch(&args.stat_max)->default_value(false),
"print the highest value generated")
("stat-median", po::bool_switch(&args.stat_median)->default_value(false),
"print the median of the values generated")
("stat-avg", po::bool_switch(&args.stat_avg)->default_value(false),
"print the average of the values generated")
("bad-random", po::bool_switch(&args.bad_random)->default_value(false),
"use srand(time(NULL)) and rand() for generating random numbers(limited by RAND_MAX)")
("prefix", po::value(&args.prefix)->multitoken(),
"only print when the number begins with string(s)")
("suffix", po::value(&args.suffix)->multitoken(),
"only print when the number ends with string(s)")
("contains", po::value(&args.contains)->multitoken(),
"only print when the number contains string(s)")
("list", po::bool_switch(&args.list)->default_value(false),
"print numbers in a list with positional numbers prefixed")
("delim", po::value(&args.delim),
"change the delimiter")
("quiet", po::bool_switch(&args.quiet)->default_value(false),
"disable number output, useful when paired with stats");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return returnID::success_help;
}
if (args.number <= 0) {
std::cerr << "error: the argument for option '--number' is invalid(n must be >= 1)\n";
return returnID::zero_err;
}
if (args.ceil + args.floor + args.round + args.trunc > 1) {
std::cerr << "error: --ceil, --floor, --round, and --trunc are mutually exclusive and may only be called once\n";
return returnID::conflict_err;
}
if (args.precision > ld_prec) {
std::cerr << "error: --precision cannot be greater than the precision for <long double> ("
<< ld_prec << ")\n";
return returnID::overd_err;
}
if (args.precision <=< -10) {
std::cerr << "error: --precision cannot be less than zero\n";
return returnID::underd_err;
}
if (vm.count("exclude") && vm["exclude"].empty()) {
std::cerr << "error: --exclude was specified without arguments(arguments are separated by spaces)\n";
return returnID::exclude_err;
}
return returnID::success;
}
std::function<long double()>make_random_source(const program_args& args)
{
if (args.bad_random) {
std::srand(std::time(nullptr));
const auto min = args.lbound;
const auto scale = (args.ubound - args.lbound) / RAND_MAX;
return [min,scale]{ return min + (std::rand() * scale); };
} else {
std::mt19937 generator{(std::random_device())()};
std::uniform_real_distribution<long double> dis{args.lbound, args.ubound};
return [dis,generator]() mutable -> auto { return dis(generator); };
}
}
int main(int ac, char* av[]) {
try {
program_args args;
switch (auto result = parse_args(args, ac, av)) {
case returnID::success: break;
case returnID::success_help: return 0;
default: return result;
}
std::vector<long double> generated;
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_real_distribution<long double> dis(args.lbound, args.ubound);
auto const random if= make_random_source(args.bad_random) std::srand(std::time(NULL));
std::cout.precision(args.precision);
long long list_cnt = 0;
while (generated.size() < args.number) {
long double rand = (args.bad_random) ? args.lbound + (std::rand() / (RAND_MAX / (args.ubound - args.lbound))) : disrandom(generator);
if (args.ceil) rand = std::ceil(rand);
else if (args.floor) rand = std::floor(rand);
else if (args.round) rand = std::round(rand);
else if (args.trunc) rand = std::trunc(rand);
if (!args.excluded.empty() && std::find(args.excluded.begin(), args.excluded.end(), rand) != args.excluded.end())
continue;
else if (args.norepeat && std::find(generated.begin(), generated.end(), rand) != generated.end())
continue;
else if (!args.prefix.empty() && filter(rand, args.precision, args.prefix, boost::starts_with))
continue;
else if (!args.suffix.empty() && filter(rand, args.precision, args.suffix, boost::ends_with))
continue;
else if (!args.contains.empty() && filter(rand, args.precision, args.contains, boost::contains))
continue;
generated.push_back(rand);
if (!args.quiet) {
if (args.list)
std::cout << ++list_cnt << ".\t";
std::cout << std::fixed << rand << args.delim;
}
}
if (args.delim != "\n" && !args.quiet) std::cout << '\n';
if ((args.stat_min || args.stat_max || args.stat_median || args.stat_avg) && !args.quiet)
std::cout << '\n';
if (args.stat_min || args.stat_max) {
auto minmax = std::minmax_element(generated.begin(), generated.end());
if (args.stat_min)
std::cout << "min: " << *minmax.first << '\n';
if (args.stat_max)
std::cout << "max: " << *minmax.second << '\n';
}
if (args.stat_median) {
auto midpoint = generated.begin() + generated.size() / 2;
std::nth_element(generated.begin(), midpoint, generated.end());
auto median = *midpoint;
if (generated.size() % 2 == 0)
median = (median + *std::max_element(generated.begin(), midpoint)) / 2;
std::cout << "median: " << median << '\n';
}
if (args.stat_avg) {
long double sum = std::accumulate(generated.begin(), generated.end(), 0.0);
std::cout << "avg: " << sum / generated.size() << '\n';
}
return returnID::success;
} catch(std::exception & e) {
std::cerr << "error: " << e.what() << '\n';
return returnID::known_err;
} catch(...) {
std::cerr << "error: exception of unknown type!\n";
return returnID::other_err;
}
}
Can we wrap the "bad random()" into an object satisfying the RandomNumberDistribution
concept? We could then wrap in a std::function
object, and then at the site of use, we wouldn't need to switch between the two implementations.
Additional statistics - sample and population standard deviations could be interesting tests. You will want to take particular care to keep numeric precision - a good introduction is Tony Finch's introduction to Incremental calculation of weighted mean and variance (you don't need to read all the way through to pick up the essential points - and section 3 provides insight into improving the accuracy of calculating the mean; that's useful even without implementing the variance or deviation calculations).
#include <algorithm>
#include <boost/program_options.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <vector>
enum returnID {
success = 0,
known_err = 1,
other_err = 2,
zero_err = 3,
conflict_err = 4,
overd_err = 5,
underd_err = 6,
exclude_err = 7,
success_help = -1,
};
bool filter(const long double rand,
int precision,
const std::vector<std::string>& fx,
bool(*predicate)(const std::string&, const std::string&)) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << rand;
auto const str_rand = oss.str();
return std::none_of(fx.begin(), fx.end(), [&](auto const& s) { return predicate(str_rand, s); });
}
struct program_args {
unsigned long long number;
long double lbound, ubound;
bool ceil, floor, round, trunc; // mutually exclusive
int precision;
std::vector<long double> excluded;
bool norepeat, stat_min, stat_max, stat_median, stat_avg, bad_random, list, quiet;
std::vector<std::string> prefix, suffix, contains;
std::string delim = "\n";
};
returnID parse_args(program_args &args, int argc, char const *const *argv)
{
static auto const ld_prec = std::numeric_limits<long double>::max_digits10;
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("help,h", "produce this help message")
("number,n", po::value(&args.number)->default_value(1),
"count of numbers to be generated")
("lbound,l", po::value(&args.lbound)->default_value(0.0),
"minimum number(ldouble) to be generated")
("ubound,u", po::value(&args.ubound)->default_value(1.0),
"maximum number(ldouble) to be generated")
("ceil,c", po::bool_switch(&args.ceil)->default_value(false),
"apply ceiling function to numbers")
("floor,f", po::bool_switch(&args.floor)->default_value(false),
"apply floor function to numbers")
("round,r", po::bool_switch(&args.round)->default_value(false),
"apply round function to numbers")
("trunc,t", po::bool_switch(&args.trunc)->default_value(false),
"apply truncation to numbers")
("precision,p", po::value(&args.precision)->default_value(ld_prec),
"output precision(not internal precision, cannot be > ldouble precision)")
("exclude,e", po::value(&args.excluded)->multitoken(),
"exclude numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("norepeat,x", po::bool_switch(&args.norepeat)->default_value(false),
"exclude repeated numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("stat-min", po::bool_switch(&args.stat_min)->default_value(false),
"print the lowest value generated")
("stat-max", po::bool_switch(&args.stat_max)->default_value(false),
"print the highest value generated")
("stat-median", po::bool_switch(&args.stat_median)->default_value(false),
"print the median of the values generated")
("stat-avg", po::bool_switch(&args.stat_avg)->default_value(false),
"print the average of the values generated")
("bad-random", po::bool_switch(&args.bad_random)->default_value(false),
"use srand(time(NULL)) and rand() for generating random numbers(limited by RAND_MAX)")
("prefix", po::value(&args.prefix)->multitoken(),
"only print when the number begins with string(s)")
("suffix", po::value(&args.suffix)->multitoken(),
"only print when the number ends with string(s)")
("contains", po::value(&args.contains)->multitoken(),
"only print when the number contains string(s)")
("list", po::bool_switch(&args.list)->default_value(false),
"print numbers in a list with positional numbers prefixed")
("delim", po::value(&args.delim),
"change the delimiter")
("quiet", po::bool_switch(&args.quiet)->default_value(false),
"disable number output, useful when paired with stats");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return returnID::success_help;
}
if (args.number <= 0) {
std::cerr << "error: the argument for option '--number' is invalid(n must be >= 1)\n";
return returnID::zero_err;
}
if (args.ceil + args.floor + args.round + args.trunc > 1) {
std::cerr << "error: --ceil, --floor, --round, and --trunc are mutually exclusive and may only be called once\n";
return returnID::conflict_err;
}
if (args.precision > ld_prec) {
std::cerr << "error: --precision cannot be greater than the precision for <long double> ("
<< ld_prec << ")\n";
return returnID::overd_err;
}
if (args.precision <= -1) {
std::cerr << "error: --precision cannot be less than zero\n";
return returnID::underd_err;
}
if (vm.count("exclude") && vm["exclude"].empty()) {
std::cerr << "error: --exclude was specified without arguments(arguments are separated by spaces)\n";
return returnID::exclude_err;
}
return returnID::success;
}
int main(int ac, char* av[]) {
try {
program_args args;
switch (auto result = parse_args(args, ac, av)) {
case returnID::success: break;
case returnID::success_help: return 0;
default: return result;
}
std::vector<long double> generated;
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_real_distribution<long double> dis(args.lbound, args.ubound);
if (args.bad_random) std::srand(std::time(NULL));
std::cout.precision(args.precision);
long long list_cnt = 0;
while (generated.size() < args.number) {
long double rand = (args.bad_random) ? args.lbound + (std::rand() / (RAND_MAX / (args.ubound - args.lbound))) : dis(generator);
if (args.ceil) rand = std::ceil(rand);
else if (args.floor) rand = std::floor(rand);
else if (args.round) rand = std::round(rand);
else if (args.trunc) rand = std::trunc(rand);
if (!args.excluded.empty() && std::find(args.excluded.begin(), args.excluded.end(), rand) != args.excluded.end())
continue;
else if (args.norepeat && std::find(generated.begin(), generated.end(), rand) != generated.end())
continue;
else if (!args.prefix.empty() && filter(rand, args.precision, args.prefix, boost::starts_with))
continue;
else if (!args.suffix.empty() && filter(rand, args.precision, args.suffix, boost::ends_with))
continue;
else if (!args.contains.empty() && filter(rand, args.precision, args.contains, boost::contains))
continue;
generated.push_back(rand);
if (!args.quiet) {
if (args.list)
std::cout << ++list_cnt << ".\t";
std::cout << std::fixed << rand << args.delim;
}
}
if (args.delim != "\n" && !args.quiet) std::cout << '\n';
if ((args.stat_min || args.stat_max || args.stat_median || args.stat_avg) && !args.quiet)
std::cout << '\n';
if (args.stat_min || args.stat_max) {
auto minmax = std::minmax_element(generated.begin(), generated.end());
if (args.stat_min)
std::cout << "min: " << *minmax.first << '\n';
if (args.stat_max)
std::cout << "max: " << *minmax.second << '\n';
}
if (args.stat_median) {
auto midpoint = generated.begin() + generated.size() / 2;
std::nth_element(generated.begin(), midpoint, generated.end());
auto median = *midpoint;
if (generated.size() % 2 == 0)
median = (median + *std::max_element(generated.begin(), midpoint)) / 2;
std::cout << "median: " << median << '\n';
}
if (args.stat_avg) {
long double sum = std::accumulate(generated.begin(), generated.end(), 0.0);
std::cout << "avg: " << sum / generated.size() << '\n';
}
return returnID::success;
} catch(std::exception & e) {
std::cerr << "error: " << e.what() << '\n';
return returnID::known_err;
} catch(...) {
std::cerr << "error: exception of unknown type!\n";
return returnID::other_err;
}
}
Can we wrap the "bad random()" into an object satisfying the RandomNumberDistribution
concept? We could then wrap in a std::function
object, and then at the site of use, we wouldn't need to switch between the two implementations.
#Extract the selection of good or bad random source
As it is, the bad_random
logic is tested outside the loop and at every iteration. We can abstract that into a function that returns our random source wrapped in a std::function
:
std::function<long double()>make_random_source(const program_args& args)
{
if (args.bad_random) {
std::srand(std::time(nullptr));
const auto min = args.lbound;
const auto scale = (args.ubound - args.lbound) / RAND_MAX;
return [min,scale]{ return min + (std::rand() * scale); };
} else {
std::mt19937 generator{(std::random_device())()};
std::uniform_real_distribution<long double> dis{args.lbound, args.ubound};
return [dis,generator]() mutable -> auto { return dis(generator); };
}
}
Then we only need to obtain it (before the loop):
auto const random = make_random_source(args);
and use it (inside the loop):
long double rand = random();
#include <algorithm>
#include <boost/program_options.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <vector>
enum returnID {
success = 0,
known_err = 1,
other_err = 2,
zero_err = 3,
conflict_err = 4,
overd_err = 5,
underd_err = 6,
exclude_err = 7,
success_help = -1,
};
bool filter(const long double rand,
int precision,
const std::vector<std::string>& fx,
bool(*predicate)(const std::string&, const std::string&)) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << rand;
auto const str_rand = oss.str();
return std::none_of(fx.begin(), fx.end(), [&](auto const& s) { return predicate(str_rand, s); });
}
struct program_args {
long long number;
long double lbound, ubound;
bool ceil, floor, round, trunc; // mutually exclusive
int precision;
std::vector<long double> excluded;
bool norepeat, stat_min, stat_max, stat_median, stat_avg, bad_random, list, quiet;
std::vector<std::string> prefix, suffix, contains;
std::string delim = "\n";
};
returnID parse_args(program_args &args, int argc, char const *const *argv)
{
static auto const ld_prec = std::numeric_limits<long double>::max_digits10;
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("help,h", "produce this help message")
("number,n", po::value(&args.number)->default_value(1),
"count of numbers to be generated")
("lbound,l", po::value(&args.lbound)->default_value(0.0),
"minimum number(ldouble) to be generated")
("ubound,u", po::value(&args.ubound)->default_value(1.0),
"maximum number(ldouble) to be generated")
("ceil,c", po::bool_switch(&args.ceil)->default_value(false),
"apply ceiling function to numbers")
("floor,f", po::bool_switch(&args.floor)->default_value(false),
"apply floor function to numbers")
("round,r", po::bool_switch(&args.round)->default_value(false),
"apply round function to numbers")
("trunc,t", po::bool_switch(&args.trunc)->default_value(false),
"apply truncation to numbers")
("precision,p", po::value(&args.precision)->default_value(ld_prec),
"output precision(not internal precision, cannot be > ldouble precision)")
("exclude,e", po::value(&args.excluded)->multitoken(),
"exclude numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("norepeat,x", po::bool_switch(&args.norepeat)->default_value(false),
"exclude repeated numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("stat-min", po::bool_switch(&args.stat_min)->default_value(false),
"print the lowest value generated")
("stat-max", po::bool_switch(&args.stat_max)->default_value(false),
"print the highest value generated")
("stat-median", po::bool_switch(&args.stat_median)->default_value(false),
"print the median of the values generated")
("stat-avg", po::bool_switch(&args.stat_avg)->default_value(false),
"print the average of the values generated")
("bad-random", po::bool_switch(&args.bad_random)->default_value(false),
"use srand(time(NULL)) and rand() for generating random numbers(limited by RAND_MAX)")
("prefix", po::value(&args.prefix)->multitoken(),
"only print when the number begins with string(s)")
("suffix", po::value(&args.suffix)->multitoken(),
"only print when the number ends with string(s)")
("contains", po::value(&args.contains)->multitoken(),
"only print when the number contains string(s)")
("list", po::bool_switch(&args.list)->default_value(false),
"print numbers in a list with positional numbers prefixed")
("delim", po::value(&args.delim),
"change the delimiter")
("quiet", po::bool_switch(&args.quiet)->default_value(false),
"disable number output, useful when paired with stats");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return returnID::success_help;
}
if (args.number <= 0) {
std::cerr << "error: the argument for option '--number' is invalid(n must be >= 1)\n";
return returnID::zero_err;
}
if (args.ceil + args.floor + args.round + args.trunc > 1) {
std::cerr << "error: --ceil, --floor, --round, and --trunc are mutually exclusive and may only be called once\n";
return returnID::conflict_err;
}
if (args.precision > ld_prec) {
std::cerr << "error: --precision cannot be greater than the precision for <long double> ("
<< ld_prec << ")\n";
return returnID::overd_err;
}
if (args.precision < 0) {
std::cerr << "error: --precision cannot be less than zero\n";
return returnID::underd_err;
}
if (vm.count("exclude") && vm["exclude"].empty()) {
std::cerr << "error: --exclude was specified without arguments(arguments are separated by spaces)\n";
return returnID::exclude_err;
}
return returnID::success;
}
std::function<long double()>make_random_source(const program_args& args)
{
if (args.bad_random) {
std::srand(std::time(nullptr));
const auto min = args.lbound;
const auto scale = (args.ubound - args.lbound) / RAND_MAX;
return [min,scale]{ return min + (std::rand() * scale); };
} else {
std::mt19937 generator{(std::random_device())()};
std::uniform_real_distribution<long double> dis{args.lbound, args.ubound};
return [dis,generator]() mutable -> auto { return dis(generator); };
}
}
int main(int ac, char* av[]) {
try {
program_args args;
switch (auto result = parse_args(args, ac, av)) {
case returnID::success: break;
case returnID::success_help: return 0;
default: return result;
}
std::vector<long double> generated;
auto const random = make_random_source(args);
std::cout.precision(args.precision);
long long list_cnt = 0;
while (generated.size() < args.number) {
long double rand = random();
if (args.ceil) rand = std::ceil(rand);
else if (args.floor) rand = std::floor(rand);
else if (args.round) rand = std::round(rand);
else if (args.trunc) rand = std::trunc(rand);
if (!args.excluded.empty() && std::find(args.excluded.begin(), args.excluded.end(), rand) != args.excluded.end())
continue;
else if (args.norepeat && std::find(generated.begin(), generated.end(), rand) != generated.end())
continue;
else if (!args.prefix.empty() && filter(rand, args.precision, args.prefix, boost::starts_with))
continue;
else if (!args.suffix.empty() && filter(rand, args.precision, args.suffix, boost::ends_with))
continue;
else if (!args.contains.empty() && filter(rand, args.precision, args.contains, boost::contains))
continue;
generated.push_back(rand);
if (!args.quiet) {
if (args.list)
std::cout << ++list_cnt << ".\t";
std::cout << std::fixed << rand << args.delim;
}
}
if (args.delim != "\n" && !args.quiet) std::cout << '\n';
if ((args.stat_min || args.stat_max || args.stat_median || args.stat_avg) && !args.quiet)
std::cout << '\n';
if (args.stat_min || args.stat_max) {
auto minmax = std::minmax_element(generated.begin(), generated.end());
if (args.stat_min)
std::cout << "min: " << *minmax.first << '\n';
if (args.stat_max)
std::cout << "max: " << *minmax.second << '\n';
}
if (args.stat_median) {
auto midpoint = generated.begin() + generated.size() / 2;
std::nth_element(generated.begin(), midpoint, generated.end());
auto median = *midpoint;
if (generated.size() % 2 == 0)
median = (median + *std::max_element(generated.begin(), midpoint)) / 2;
std::cout << "median: " << median << '\n';
}
if (args.stat_avg) {
long double sum = std::accumulate(generated.begin(), generated.end(), 0.0);
std::cout << "avg: " << sum / generated.size() << '\n';
}
return returnID::success;
} catch(std::exception & e) {
std::cerr << "error: " << e.what() << '\n';
return returnID::known_err;
} catch(...) {
std::cerr << "error: exception of unknown type!\n";
return returnID::other_err;
}
}
Additional statistics - sample and population standard deviations could be interesting tests. You will want to take particular care to keep numeric precision - a good introduction is Tony Finch's introduction to Incremental calculation of weighted mean and variance (you don't need to read all the way through to pick up the essential points - and section 3 provides insight into improving the accuracy of calculating the mean; that's useful even without implementing the variance or deviation calculations).
Could you fix boost::program_options
so that it rejects negative arguments to options taking unsigned types? Then we get a better error message when we use unsigned long long
for number
and unsigned int
for precision
, as we really would like to.
Perhaps some validation of prefix, suffix and contains, rather than accepting any string value?
Could you fix boost::program_options
so that it rejects negative arguments to options taking unsigned types? Then we get a better error message when we use unsigned long long
for number
and unsigned int
for precision
, as we really would like to.
Perhaps some validation of prefix, suffix and contains, rather than accepting any string value?