5
\$\begingroup\$

I've a function to generate random characters from a set of alphabets. This function would be called multiple times and thus I'm trying to make it use same set of variables, i.e. have same seed so that strings don't repeat as long as possible.

#include <iostream>
#include <random>
#include <string>

std::string generateRandomChar(const unsigned int _len)
    {
        std::string result;
        result.reserve(_len);

        static constexpr char alphanum[] = "0123456789"
                                           "abcdefghijklmnopqrstuvwxyz"
                                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(0, 61);

        for (int i = 0; i < _len; 
            result += (alphanum[dis(gen)]);
        }
        return result;
    }

int main(){
    for(int i = 0; i < 10; ++i){
        std::cout << generateRandomChar(10) << std::endl;
    }
}

Unfortunately I don't have any expertise with C++11 <random> functions and I was only using srand and friends earlier, so I might be making lots of mistakes here. Currently it works and generates tons of strings without repeating, but I'm sure I could make either of:

  • std::random_device rd;
  • std::mt19937 gen(rd());
  • std::uniform_int_distribution<> dis(0, 61);

static variable too, so that it isn't calculated each time the function is called because that would be waste right?

So which one should be static here? Are there any mistakes/improvements that you see here?

Ideone

\$\endgroup\$
  • \$\begingroup\$ I am not sure whether the fact that your output yields results that are unique if called multiple times (a low number of times I might add), is anything but the result of your rd, mt19937 and dis call. So I think that making those static and not on a per call base is a bad plan. \$\endgroup\$ – Quant Sep 21 '16 at 9:49
  • \$\begingroup\$ @Quant I'm sorry I couldn't understand your suggestion. I'm no good with English language and it's quite long sentence. would you mind explaining it. I'm not guaranteeing uniqueness, but I'm trying to not regenerate multiple times if more common words are generated. For perfect uniqueness, I know there must exist one to one correspondence function but it won't be easy as far as I know. \$\endgroup\$ – hg_git Sep 21 '16 at 11:20
2
\$\begingroup\$

Wherever you place static will change the behaviour of your program tremendously. In the end, we have \$2^3 = 8\$ combinations, so let's check some of them.

A random_device may fall back to a pseudorandom engine if no hardware-device for random numbers is available. If you don't use static on random_device in this case, all your strings may end up the same, as the pseudorandom engine may get initialized with a implementation defined fixed seed, e.g.

 std::random_device a;
 std::random_device b;
 assert(a() == b());  // worst case if no hardware-device or a low-effort implementation is used

In this case, a mt19937 would get seeded with the same value all the time, and we end up with the same string in every call. This changes if either mt19937 or random_device is static, and may change if dist is static, as a uniform_int_distribution can use several calls to the generator for its internal state.

Of course, if mt19937 is static, you may as well change random_device to a static variant, as it will get evaluated only once. This is the closest your get to a single srand() call at the start of your program. Your uniform_int_distribution still loses its internal state, though.

In the end, it depends on whether we want two generateRandomChar calls to correlate to each other. If they shall not correlate at all, we need to reseed our PRNG with a new non-deterministic number and cannot use static at all.

Or, to put it in another way: if we use static for all three values, then

generateRandomChar(10) + generateRandomChar(10)

is the same as generateRandomChar(20) If we use static on mt19937, then both may be the same, depending on both _len and dist.

Either way, if you want to have more control over the generation, you can just provide a generator in your function:

template <typename Generator>
std::string generateRandomChar(Generator & gen, const unsigned int _len)
    {
        ...
    }

Same holds for the distribution.


Other than that, I'd use sizeof instead of 61 to make sure that I don't accidentally change the pool of possible characters without changing the number bounds in dist's constructor:

static constexpr char alphanum[] = "0123456789"
                                   "abcdefghijklmnopqrstuvwxyz"
                                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, sizeof(alphanum) - 1);
\$\endgroup\$
-1
\$\begingroup\$

I think you should create a separate wrapper class for this occasion. I used the following:

#include <random>
class randomStreamUniformInt {
public:
    explicit randomStreamUniformInt(int lower_bound, int upper_bound)
    : mt(rand()), uniform_dist(lower_bound, upper_bound) {}
    explicit randomStreamUniformInt(int lower_bound, int upper_bound, double seed)
    : mt(seed), uniform_dist(lower_bound, upper_bound) {}

    int operator ()(void) { return uniform_dist(mt); }
private:
    std::mt19937_64                     mt;
    std::uniform_int_distribution<>     uniform_dist;
};

You can obviously extend it to use your prefered generator. Then you should either pass this per reference to your function or create a class with generateRandomChar as a method.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.