1

I'm new to C++ and I am trying to create a basic genetic algorithm. I created a Chromosome class and want to create a Society class that generates a vector of these Chromosomes with randomly generated "genes". Genes being the vector in the Chromosome that holds values of 0 or 1. I was testing out the Chromosome constructor, and all of the objects have the same gene vectors. How can I make the constructor generate random values? I have included code below. Any other coding practice or optimization tips would also be extremely appreciated.

Source.cpp

#include "Chromosome.h"
#include "Society.h"

using namespace std;

int main()
{
    Chromosome demo = Chromosome::Chromosome();
    Chromosome demo2 = Chromosome::Chromosome();
    return 1;
}

Chromosome.h

#pragma once
#include <vector>
using namespace std;

class Chromosome
{
private:
    int fitness;
    vector<int> genes;

public:
    Chromosome();

    void generateGenes();

    int calculateFitness(),
        getFitness();

    vector<int> getGenes();
    void setGenes(vector<int> child);
};

Chromosome.cpp

#include "Chromosome.h"
#include <cstdlib>
#include <ctime> 
#include <numeric>
using namespace std;

Chromosome::Chromosome()
{
    generateGenes();
    Chromosome::fitness = calculateFitness();
}

void Chromosome::generateGenes()
{
    srand(time(NULL));
    for (unsigned i = 0; i < 10; i++)
    {
        unsigned chance = rand() % 5;
        Chromosome::genes.push_back((!chance)? 1 : 0);
    }
}

int Chromosome::calculateFitness()
{
    int sum = 0;
    for (unsigned i = 0; i < Chromosome::genes.size(); i++)
    {
        sum += Chromosome::genes[i];
    }
    return sum;
}

int Chromosome::getFitness()
{
    return Chromosome::fitness;
}

vector<int> Chromosome::getGenes()
{
    return Chromosome::genes;
}

void Chromosome::setGenes(vector<int> child)
{
    Chromosome::genes = child;
}
DhyeyL
  • 25
  • 5
  • 3
    Sidenote: **Never** do `using namespace std;` in header files. – Ted Lyngmo Feb 13 '20 at 20:50
  • Also note that `srand(time(NULL));` is supposed to be called _once_ in the whole program. And that C++ has it's own [random number library](https://en.cppreference.com/w/cpp/header/random), so you don't have to use C-functions any more. – Lukas-T Feb 13 '20 at 20:52

2 Answers2

5

You seed the random number generator with the same value time(NULL). Two calls after eachother will return the same time_t. You'll generate one set of random numbers first, then reset the random number generator and generate them again.

Only call srand() once during the whole program run.

Also, use <random> instead to get better/faster random number generators.

Instead of rand() % 5; using <random>:

#include <random>

// A function to return a random number generator.
inline std::mt19937& generator() {
    // the generator will only be seeded once since it's static
    static std::mt19937 gen(std::random_device{}());
    return gen;
}

// A function to generate unsigned int:s in the range [min, max]
int my_rand(unsigned  min, unsigned  max) {
    std::uniform_int_distribution<unsigned > dist(min, max);
    return dist(generator());
}

Then call it:

unsigned chance = my_rand(0, 4);
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
3

Your problem is the use of rand & srand in a C++ program.

srand(time(NULL));
unsigned chance = rand() % 5;

in this implementation, rand might return multiple numbers that will give you the same final result. for example: 19, 24, 190214, 49789, 1645879, 15623454, 4, 156489719, 1645234, 152349, ...

There are different ways of generate random numbers in C++, this one isn't recommended due to bad results.

One (of many) good ways to generate random, using "pseudo-random" in C++:

void Chromosome::generateGenes()
{
    // Initialize random
    std::random_device rd;  // Will be used to obtain a seed for the random number engine
    std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
    std::uniform_int_distribution<> dis(0, 5);

    for (unsigned i = 0; i < 10; i++)
    {
        // Use random: dis(gen);
        unsigned chance = dis(gen);
        Chromosome::genes.push_back((!chance)? 1 : 0);
    }
}

Include:

#include <random>

Right note by @TedLyngmo: Every time that function will be called (in your case, in every object creation in the constructor call), this code will make you generate a new random seed (In 'Initialize random' section). In more progress cases, or as the program grows, it is highly recommended to extract this initialize to another function (and maybe to a new class object for modular programming reason). In this response I demonstrated the general syntax of using this type of random in your case.

Read about:

Pseudo-random number generation

Uniform Distribution

Thanks to @M.M: How to succinctly, portably, and thoroughly seed the mt19937 PRNG?

Coral Kashri
  • 3,436
  • 2
  • 10
  • 22
  • It's not like `rand()` was broken by design, it's just, that it's seeded too often. – Lukas-T Feb 13 '20 at 20:58
  • 2
    also relevant reading: https://stackoverflow.com/questions/45069219/how-to-succinctly-portably-and-thoroughly-seed-the-mt19937-prng , particularly the point that `std::random_device` might [fall back](https://stackoverflow.com/questions/18880654/why-do-i-get-the-same-sequence-for-every-run-with-stdrandom-device-with-mingw) – M.M Feb 13 '20 at 20:58
  • @churill All the tests I done with this random, even when it seeded once (just like in this case), it never turned out with good results in C++ (not talking about C). – Coral Kashri Feb 13 '20 at 21:06
  • `unsigned chance = rand() % 5;` is actually _not_ the problem. Had `srand()` only been called once (and with a better source than `time()`) everything would have worked as expected. Yes, `rand()` isn't the best PRNG, but it's not _that_ bad that OP would get into this problem if it had been properly used. – Ted Lyngmo Feb 13 '20 at 21:17
  • Another note: You are recreating and seeding the generator for each call to `generateGenes()`. Seeding a generator is considered expensive so seed once and keep the generator. – Ted Lyngmo Feb 13 '20 at 21:18
  • @KorelK I agree, that it doesn't yield good results and the wording now sounds a lot less pessimistic. – Lukas-T Feb 13 '20 at 21:29
  • @TedLyngmo I accept most of the things you noted, and fixed my response. However, using `rand` and `%` to get a number is really bad idea- the issue explained in my fixed response. For the second note, all I wanted to show is the general syntax that should be used to use this type or randoms. But you right, it should be mentioned in the response, and now it is. Thanks for the notes! – Coral Kashri Feb 13 '20 at 22:51
  • Using `rand() % 5` will make the distribution _slightly_ skewed, that's true. It'd be better to use it with `uniform_int_distribution` ([example](https://godbolt.org/z/xY-bp7)). – Ted Lyngmo Feb 14 '20 at 08:48
  • The random generator is better initialized using a `std::seed_seq`, which in turn is initialized with an initializer list filled with multiple outputs of `rd()`, not just one. See also "[C++ Seeding Surprises](http://www.pcg-random.org/posts/cpp-seeding-surprises.html)". – Peter O. Feb 17 '20 at 08:34