Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <algorithm>
- #include <array>
- #include <chrono>
- #include <future>
- #include <iterator>
- #include <iostream>
- #include <ostream>
- #include <random>
- #include <stdexcept>
- #include <string>
- #include <thread>
- #include <vector>
- namespace bunny_exercise
- {
- using std::array;
- using std::back_inserter;
- using std::chrono::milliseconds;
- using std::copy;
- using std::count_if;
- using std::endl;
- using std::ostream;
- using std::default_random_engine;
- using std::find_if;
- using std::for_each;
- using std::logic_error;
- using std::remove_if;
- using std::shuffle;
- using std::sort;
- using std::string;
- using std::this_thread::sleep_for;
- using std::transform;
- using std::uniform_int_distribution;
- using std::vector;
- // Each bunny object must have Sex: Male, Female
- //
- enum class Sex { male, female };
- ostream& operator<<(ostream& os, const Sex sex)
- {
- switch (sex) {
- case Sex::male:
- os << "m";
- break;
- case Sex::female:
- os << "f";
- break;
- default:
- throw logic_error{""};
- }
- return os;
- }
- // Convenience array to ease selecting a random value
- const array<Sex, 2> sexes = { Sex::male, Sex::female };
- // Each bunny object must have color: white, brown, black, spotted
- //
- enum class Color { white, brown, black, spotted };
- ostream& operator<<(ostream& os, const Color color)
- {
- switch (color) {
- case Color::white:
- os << "white";
- break;
- case Color::brown:
- os << "brown";
- break;
- case Color::black:
- os << "black";
- break;
- case Color::spotted:
- os << "spotted";
- break;
- default:
- throw logic_error{""};
- }
- return os;
- }
- // Convenience array to ease indexing and selecting a random value
- const array<Color, 4> colors = { Color::white, Color::brown, Color::black, Color::spotted };
- // Each bunny object must have Name randomly chosen from a list of bunny names
- //
- const array<string, 12> names = {
- "Babbitty", "Basil", "Benjamin", "Bigwig",
- "Bionic", "Br'er", "Bunnicula", "Bunnsy",
- "Janet", "Buster", "Cecily", "Cottontail"
- };
- // Bunny objects
- // There's no invariant that isn't already enforced by the type system,
- // so a simple struct will suffice
- //
- struct Bunny {
- Sex sex;
- Color color;
- unsigned int age;
- string name;
- bool is_radioactive_mutant_vampire;
- };
- // If a bunny becomes older than 10 years old, it dies
- // Radioactive vampire bunnies do not die until they reach age 50
- //
- const auto max_age = 10;
- const auto radioactive_mutant_vampire_max_age = 50;
- auto is_alive(const Bunny& bunny)
- {
- return (
- !bunny.is_radioactive_mutant_vampire && bunny.age < max_age ||
- bunny.is_radioactive_mutant_vampire && bunny.age < radioactive_mutant_vampire_max_age
- );
- }
- // So long as there is at least one male age 2 or older,
- // for each female bunny in the list age 2 or older, a new bunny is created each turn
- // Radioactive vampire bunnies are excluded from regular breeding and do not count as adult bunnies
- //
- const auto breedable_min_age = 2;
- auto can_breed(const Bunny& bunny)
- {
- return is_alive(bunny) && bunny.age >= breedable_min_age && !bunny.is_radioactive_mutant_vampire;
- }
- // The program should print all the bunnies details
- //
- ostream& operator<<(ostream& os, const Bunny& bunny)
- {
- if (bunny.is_radioactive_mutant_vampire) {
- os << "Radioactive Mutant Vampire ";
- }
- os << "Bunny " << bunny.name << ", " << bunny.age << "/" << bunny.sex;
- if (!is_alive(bunny)) {
- os << ", is dead";
- }
- return os;
- }
- // Each bunny object must have
- // Sex: Male, Female (random at creation 50/50)
- // Color: white, brown, black, spotted (either inherited or randomly chosen)
- // Name: randomly chosen at creation from a list of bunny names
- // radioactive_mutant_vampire_bunny: true/false (decided at time of bunny creation 2% chance of true)
- //
- // This was almost just a pair of functions, but since they both need a random engine,
- // it seemed better to scope that random engine to just these two functions
- //
- const auto percent_chance_born_radioactive = 2;
- class Builder
- {
- public:
- auto make_random(Color color)
- {
- const auto random_sex = sexes.at(random_sex_distribution_(random_engine_));
- const auto random_name = names.at(random_name_distribution_(random_engine_));
- const auto random_is_radioactive = random_radioactive_distribution_(random_engine_) <= percent_chance_born_radioactive;
- return Bunny{random_sex, color, 0, random_name, random_is_radioactive};
- }
- auto make_random()
- {
- const auto random_color = colors.at(random_color_distribution_(random_engine_));
- return make_random(random_color);
- }
- private:
- default_random_engine random_engine_;
- uniform_int_distribution<decltype(colors)::size_type> random_color_distribution_{0, colors.size() - 1};
- uniform_int_distribution<decltype(sexes)::size_type> random_sex_distribution_{0, sexes.size() - 1};
- uniform_int_distribution<decltype(names)::size_type> random_name_distribution_{0, names.size() - 1};
- uniform_int_distribution<> random_radioactive_distribution_{1, 100};
- };
- // The program should print a list of all the bunnies in the colony each turn along w/ all the bunnies details, sorted by age
- //
- ostream& operator<<(ostream& os, const vector<Bunny>& bunnies)
- {
- // We need to sort the list but we don't want to alter the original array,
- // so make an array of pointers
- vector<const Bunny*> bunnies_ptrs;
- transform(bunnies.begin(), bunnies.end(), back_inserter(bunnies_ptrs), [] (const Bunny& bunny) {
- return &bunny;
- });
- // Sort by age
- sort(bunnies_ptrs.begin(), bunnies_ptrs.end(), [] (const Bunny* a, const Bunny* b) {
- return a->age < b->age;
- });
- os << "---- begin newborns\n";
- for_each(bunnies_ptrs.begin(), bunnies_ptrs.end(), [&os] (const Bunny* bunny) {
- if (bunny->age == 0) {
- os << *bunny << "\n";
- }
- });
- os << "---- end newborns\n";
- os << "---- begin population\n";
- for_each(bunnies_ptrs.begin(), bunnies_ptrs.end(), [&os] (const Bunny* bunny) {
- os << *bunny << "\n";
- });
- os << "---- end population\n";
- os << "\n####\n" << endl;
- return os;
- }
- // Operate on copy to favor immutability at the call site and to simplify concurrency
- //
- auto next_generation(vector<Bunny> bunnies, Builder* builder)
- {
- // If a radioactive mutant vampire bunny is born then each turn it will
- // change exactly one non radioactive bunny into a radioactive vampire bunny
- // But the problem description didn't specify how we should pick the bunnies to infect
- // I could just iterate through the array left to right, but then I'll always pick the oldest bunnies first
- // I'd rather pick bunnies to infect at random
- // std::sample would be perfect, but it isn't available yet in compilers,
- // so instead I'll shuffle the bunnies then iterate left to right
- // But we don't want to alter the original array, so make an array of pointers
- vector<Bunny*> bunnies_ptrs;
- transform(bunnies.begin(), bunnies.end(), back_inserter(bunnies_ptrs), [] (Bunny& bunny) {
- return &bunny;
- });
- shuffle(bunnies_ptrs.begin(), bunnies_ptrs.end(), default_random_engine{});
- // If a radioactive mutant vampire bunny is born then each turn it will
- // change exactly one non radioactive bunny into a radioactive vampire bunny
- // If there are two radioactive mutant vampire bunnies two bunnies will be changed each turn and so on...
- auto n_radioactive_bunnies = count_if(bunnies.begin(), bunnies.end(), [] (const Bunny& bunny) {
- return bunny.is_radioactive_mutant_vampire;
- });
- for_each(bunnies_ptrs.begin(), bunnies_ptrs.end(), [&n_radioactive_bunnies] (Bunny* bunny) {
- if (n_radioactive_bunnies > 0 && is_alive(*bunny) && !bunny->is_radioactive_mutant_vampire) {
- bunny->is_radioactive_mutant_vampire = true;
- --n_radioactive_bunnies;
- }
- });
- // So long as there is at least one male age 2 or older,
- // for each female bunny in the list age 2 or older, a new bunny is created each turn
- // If there was 1 adult male and 3 adult female bunnies, three new bunnies would be born each turn
- auto has_breedable_male = find_if(bunnies.begin(), bunnies.end(), [] (const Bunny& bunny) {
- return can_breed(bunny) && bunny.sex == Sex::male;
- }) != bunnies.end();
- vector<Bunny> new_bunnies;
- if (has_breedable_male) {
- for_each(bunnies.begin(), bunnies.end(), [&new_bunnies, builder] (const Bunny& bunny) {
- // Filter for breed-able females
- if (!can_breed(bunny) || bunny.sex != Sex::female) return;
- // New bunnies born should be the same color as their mother
- new_bunnies.push_back(builder->make_random(bunny.color));
- });
- }
- // Each turn the bunnies age 1 year (the newborns haven't gone a turn yet)
- for_each(bunnies.begin(), bunnies.end(), [] (Bunny& bunny) {
- ++bunny.age;
- });
- // If a bunny becomes older than 10 years old, it dies
- remove_if(bunnies.begin(), bunnies.end(), [] (const Bunny& bunny) {
- return !is_alive(bunny);
- });
- // Add the newborns to the population
- copy(new_bunnies.begin(), new_bunnies.end(), back_inserter(bunnies));
- // Pretend long complex operation
- // This gives me an excuse to play with concurrency
- sleep_for(milliseconds(4000));
- return bunnies;
- }
- }
- int main(const int argc, const char* const argv[])
- {
- using bunny_exercise::Builder;
- using bunny_exercise::Bunny;
- using bunny_exercise::next_generation;
- using std::async;
- using std::chrono::milliseconds;
- using std::cout;
- using std::this_thread::sleep_for;
- using std::vector;
- Builder builder;
- // At program initialization 5 bunnies must be created and given random colors
- vector<Bunny> bunnies;
- for (auto n_repeat = 5; n_repeat > 0; --n_repeat) {
- bunnies.push_back(builder.make_random());
- }
- cout << bunnies;
- do {
- // Compute the next generation concurrently with the 5s waiting period,
- // otherwise we'll be waiting the 5s plus however long this computation takes
- auto future_bunnies = async(next_generation, bunnies, &builder);
- sleep_for(milliseconds(5000));
- bunnies = future_bunnies.get();
- cout << bunnies;
- } while (true);
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement