Skip to main content
Code Review

Return to Answer

Commonmark migration
Source Link

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?

Show how to wrap random source in a function object, in response to comment.
Source Link
Toby Speight
  • 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).

added 359 characters in body
Source Link
Toby Speight
  • 88k
  • 14
  • 104
  • 325

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?

Missed a deducible template argument
Source Link
Toby Speight
  • 88k
  • 14
  • 104
  • 325
Loading
Fixed arithmetic error in median
Source Link
Toby Speight
  • 88k
  • 14
  • 104
  • 325
Loading
Source Link
Toby Speight
  • 88k
  • 14
  • 104
  • 325
Loading
lang-cpp

AltStyle によって変換されたページ (->オリジナル) /