For fun, and to refresh my memory on Boost's program_options library, I have written a program with the goal of randomly generating numbers with loads of useless(-ish) features. I have termed it diceroll.
#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};
const int ld_prec = std::numeric_limits<long double>::max_digits10;
enum fx_enum {prefix = 0, suffix = 1, contains = 2};
bool fx_vect(const long double & rand, const std::vector<std::string> & fx, const int & state);
int main(int ac, char* av[]) {
try {
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";
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("help,h", "produce this help message")
("number,n", po::value<long long>(&number)->default_value(1),
"count of numbers to be generated")
("lbound,l", po::value<long double>(&lbound)->default_value(0.0),
"minimum number (ldouble) to be generated")
("ubound,u", po::value<long double>(&ubound)->default_value(1.0),
"maximum number (ldouble) to be generated")
("ceil,c", po::bool_switch(&ceil)->default_value(false),
"apply ceiling function to numbers")
("floor,f", po::bool_switch(&floor)->default_value(false),
"apply floor function to numbers")
("round,r", po::bool_switch(&round)->default_value(false),
"apply round function to numbers")
("trunc,t", po::bool_switch(&trunc)->default_value(false),
"apply truncation to numbers")
("precision,p", po::value<int>(&precision)->default_value(ld_prec),
"output precision (not internal precision, cannot be > ldouble precision)")
("exclude,e", po::value<std::vector<long double> >(&excluded)->multitoken(),
"exclude numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("norepeat,x", po::bool_switch(&norepeat)->default_value(false),
"exclude repeated numbers from being printed, best with --ceil, --floor, --round, or --trunc")
("stat-min", po::bool_switch(&stat_min)->default_value(false),
"print the lowest value generated")
("stat-max", po::bool_switch(&stat_max)->default_value(false),
"print the highest value generated")
("stat-median", po::bool_switch(&stat_median)->default_value(false),
"print the median of the values generated")
("stat-avg", po::bool_switch(&stat_avg)->default_value(false),
"print the average of the values generated")
("bad-random", po::bool_switch(&bad_random)->default_value(false),
"use srand(time(NULL)) and rand() for generating random numbers (limited by RAND_MAX)")
("prefix", po::value<std::vector<std::string> >(&prefix)->multitoken(),
"only print when the number begins with string(s)")
("suffix", po::value<std::vector<std::string> >(&suffix)->multitoken(),
"only print when the number ends with string(s)")
("contains", po::value<std::vector<std::string> >(&contains)->multitoken(),
"only print when the number contains string(s)")
("list", po::bool_switch(&list)->default_value(false),
"print numbers in a list with positional numbers prefixed")
("delim", po::value<std::string>(&delim),
"change the delimiter")
("quiet", po::bool_switch(&quiet)->default_value(false),
"disable number output, useful when paired with stats");
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if(vm.count("help")) {
std::cout << desc << '\n';
return returnID::success;
} else if(number <= 0) {
std::cerr << "error: the argument for option '--number' is invalid (n must be >= 1)\n";
return returnID::zero_err;
} else if(ceil + floor + round + trunc > 1) {
std::cerr << "error: --ceil, --floor, --round, and --trunc are mutually exclusive and may only be called once\n";
return returnID::conflict_err;
} else if(precision > ld_prec) {
std::cerr << "error: --precision cannot be greater than the precision for <long double> ("
<< ld_prec << ")\n";
return returnID::overd_err;
} else if(precision <= -1) {
std::cerr << "error: --precision cannot be less than zero\n";
return returnID::underd_err;
} else 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;
} else {
std::vector<long double> repeated, generated;
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_real_distribution<long double> dis(lbound, ubound);
if(bad_random) std::srand(std::time(NULL));
std::cout.precision(precision);
long long list_cnt = 1;
for(long long i = 1; i <= number; i++) {
if(list) ++list_cnt;
long double rand = (bad_random) ? lbound + (std::rand() / (RAND_MAX / (ubound - lbound))) : dis(generator);
if(ceil) rand = std::ceil(rand);
else if(floor) rand = std::floor(rand);
else if(round) rand = std::round(rand);
else if(trunc) rand = std::trunc(rand);
if(vm.count("exclude") && std::find(excluded.begin(), excluded.end(), rand) != excluded.end())
continue;
else if(norepeat && std::find(repeated.begin(), repeated.end(), rand) != repeated.end())
continue;
else if(vm.count("prefix") && fx_vect(rand, prefix, fx_enum::prefix))
continue;
else if(vm.count("suffix") && fx_vect(rand, suffix, fx_enum::suffix))
continue;
else if(vm.count("contains") && fx_vect(rand, contains, fx_enum::contains))
continue;
if(list && !quiet) std::cout << list_cnt << ".\t";
if(!quiet) std::cout << std::fixed << rand << delim;
if(norepeat) repeated.push_back(rand);
generated.push_back(rand);
}
if(delim != "\n" && !quiet) std::cout << '\n';
if((stat_min || stat_max || stat_median || stat_avg) && !quiet)
std::cout << '\n';
if(stat_min)
std::cout << "min: " << *std::min_element(generated.begin(), generated.end()) << '\n';
if(stat_max)
std::cout << "max: " << *std::max_element(generated.begin(), generated.end()) << '\n';
if(stat_median) {
std::sort(generated.begin(), generated.end());
auto median = (generated.size() % 2) ? generated[generated.size() / 2]
: (generated[generated.size() / 2 - 1] + generated[generated.size() / 2]) / 2;
std::cout << "median: " << median << '\n';
}
if(stat_avg) {
long double sum = 0.0;
for(auto i : generated)
sum += i;
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;
}
}
bool fx_vect(const long double & rand, const std::vector<std::string> & fx, const int & state) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(ld_prec) << rand;
std::string str_rand = oss.str();
for(auto i : fx) {
if((state == fx_enum::prefix && boost::starts_with(str_rand, i))
|| (state == fx_enum::suffix && boost::ends_with(str_rand, i))
|| (state == fx_enum::contains && boost::contains(str_rand, i))) return false;
}
return true;
}
Here's a breakdown:
The program exits through many paths, so to manage this, I use an enum named
returnID
. This avoids the use of magic numbers inmain()
.The variable
ld_prec
is the maximum length of a long double. This is used in the functionfx_vect()
and inmain()
.As said before, I am using Boost's program_options library. Here is the help text:
./diceroll -h
Options:
-h [ --help ] produce this help message
-n [ --number ] arg (=1) count of numbers to be generated
-l [ --lbound ] arg (=0) minimum number (ldouble) to be generated
-u [ --ubound ] arg (=1) maximum number (ldouble) to be generated
-c [ --ceil ] apply ceiling function to numbers
-f [ --floor ] apply floor function to numbers
-r [ --round ] apply round function to numbers
-t [ --trunc ] apply truncation to numbers
-p [ --precision ] arg (=21) output precision (not internal precision, cannot
be > ldouble precision)
-e [ --exclude ] arg exclude numbers from being printed, best with
--ceil, --floor, --round, or --trunc
-x [ --norepeat ] exclude repeated numbers from being printed,
best with --ceil, --floor, --round, or --trunc
--stat-min print the lowest value generated
--stat-max print the highest value generated
--stat-median print the median of the values generated
--stat-avg print the average of the values generated
--bad-random use srand(time(NULL)) and rand() for generating
random numbers (limited by RAND_MAX)
--prefix arg only print when the number begins with string(s)
--suffix arg only print when the number ends with string(s)
--contains arg only print when the number contains string(s)
--list print numbers in a list with positional numbers
prefixed
--delim arg change the delimiter
--quiet disable number output, useful when paired with
stats
If you're wondering what a piece of code does, check this help text, I try to explain thing as best as I can. Let me know if any of this help text is unclear.
The flags
--ceil
,--floor
,--round
, and--trunc
are mutually exclusive. I also return an error if the specified--precision
is greater than the precision of a long double (likely won't end up good).I generate random numbers using a statistically psudorandom method, but if
--bad-random
is specified, I use the bad method.For
--exclude
,--norepeat
,--prefix
,--suffix
, and--contains
, their if statements all result incontinue
, but I split them up intoif-else
to make it more readable. If it makes sense to condense them, let me know.At the end(-ish) of the code I print out the statistical variables (min, max, median, average).
The flags
--prefix
,--suffix
, and--contains
use the enumfx_enum
to once again avoid magic numbers.
I am sure my breakdown of the code is incomplete, and if something is confusing please let me know (that's part of the reason I post my code projects here, so I get good feedback!).
I would like to improve the code's readability, I have gotten used to reading it, so my perspective's skewed. I am also wondering if there is optimization that can be done, both in the logic, structure, and how I get things done.
--seed
option? And how do we specify the distribution to use? We want more of the "useless" options! (P.S. I'll post a proper review as an answer). \$\endgroup\$