Random String Selection From Library

I would like to use "cout" to display a randomly selected string from a library. For my specific project I would like to randomly generate a first name for a game character.
Say I wanted to choose one of the 3 names: Bob, Mike or Sam. No need for the full code, just an idea of how I could do this.
Create a container (std::vector, for example) to hold your strings and populate it with the names you want to randomly choose from.

Create a C++ random engine and a uniform int distribution that spans the number of names, 0 to size - 1,

Choose a name using the distribution and the random engine to generate a random number.

Output the element that matches the random number.

Done!
Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <random>
#include <iostream>
#include <array>

std::mt19937 rng(std::random_device {}());

int main() {
	constexpr std::array names { "Bob", "Mike", "Sam", "Tom", "Dick", "Harry" };
	std::uniform_int_distribution<size_t> distrib(0, names.size() - 1);
	const auto& gameChar { names[distrib(rng)] };

	std::cout << "Game character name is " << gameChar << '\n';
}

Or consider :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <random>
#include <iostream>
#include <vector>
// it could be a simple function, but...
struct randomGenerator {

    int rGenerator(const int min, const int max)
    {
	static std::random_device rd;
	static std::mt19937 rng{ rd() };
	static std::uniform_int_distribution<int> uid(min, max);
	return uid(rng);
    }
};

int main()
{   // your names
    std::vector<std::string> names{ "Bob", "Mike", "Sam", "Tom", "Dick", "Harry" };
    // object for a randomized integer according to your vector above
    randomGenerator *rdm = new randomGenerator();
    // generates an integer which will be index
    std::cout << "Player name is " << names[rdm->rGenerator(0, names.size() - 1)] << std::endl;
    // delete object
    delete rdm;

    return 0;
}
Last edited on
> static std::uniform_int_distribution<int> uid(min, max); // line 11

This variable, declared at block scope with the specifier static, is initialized once; the first time when control passes through its declaration.
On all subsequent calls to the function, the declaration is skipped (there is no re-initialisation).
More info: https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <concepts>
#include <random>
#include <ranges>
#include <string>
#include <string_view>
#include <deque>

namespace utility
{
    template < std::integral INT_TYPE > INT_TYPE rand( INT_TYPE minv, INT_TYPE maxv ) // invariant: minv <= maxv
    {
        static std::mt19937 rng( std::random_device{}() ) ; // TO DO: warm up the twister if required
        static std::uniform_int_distribution<INT_TYPE> distrib ; // initiaised once
        using param_type = decltype(distrib)::param_type ;

        distrib.param( param_type{ minv, maxv } ) ; // so we need to reset the parameters on each call
        return distrib(rng) ;
    }

    template < std::ranges::random_access_range RANGE >
    decltype(auto) pick_one_from( const RANGE& range ) // invariant: !std::ranges::empty(range)
    {
        return std::ranges::begin(range) [ rand( std::ranges::range_size_t<RANGE>(0), std::ranges::size(range) - 1 ) ] ;
    }
}

int main() // usage example
{
    std::cout << utility::rand( -8'000'000'000'000LL, 0LL ) << '\n' ;

    const std::string_view names [] { "Bob", "Mike", "Sam", "Tom", "Dick", "Harry" };
    std::cout << utility::pick_one_from(names) << '\n' ;

    const std::deque<int> numbers {1,6,8,0,3,7,5,4,9,2} ;
    std::cout << utility::pick_one_from(numbers) << '\n' ;
}

https://coliru.stacked-crooked.com/a/14ddbc17ff3c8719
the confusing part is 'from a library'. Typically you would just store the strings in a file, is that what you want/meant? It works about the same way, you read the file up front into storage and pick one randomly same as above.
Last edited on
Consider using std::sample for this:
1
2
3
4
5
6
7
8
9
std::default_random_engine rng;
template <std::ranges::input_range R>
    requires std::semiregular<std::ranges::range_value_t<R>>
  std::ranges::range_value_t<R> sample_one(R&& r)
  {
    std::ranges::range_value_t<R> result; 
    std::ranges::sample(r, std::addressof(result), 1, rng);
    return result;
  }
Last edited on
@JLBorges
The std::uniform_int_distribution<> is such a lightweight object, why not just construct and use as needed?

11
12
13
14
15
    template < std::integral INT_TYPE > INT_TYPE rand( INT_TYPE minv, INT_TYPE maxv ) // invariant: minv <= maxv
    {
        static std::mt19937 rng( std::random_device{}() ) ; // TO DO: warm up the twister if required
        return std::uniform_int_distribution<INT_TYPE> ( minv, maxv )( rng );
    }

(Honest question. Does modifying the extant object produce better object code or less latency?)
> Consider using std::sample for this

Even for a random access range, and a sample size of one, the complexity of std::ranges::sample may be O(N).


> The std::uniform_int_distribution<> is such a lightweight object, why not just construct and use as needed?

The generation of the next number may require multiple calls to the random bit generator ("amortised constant number of invocations"). If we reuse the distribution which already holds previous state, it may (repeat may) need fewer additional calls. Resetting the param is inexpensive; it has O(1) complexity.
@Geckoo - why use dynamic memory to hold one value?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <random>
#include <iostream>
#include <array>
#include <concepts>

struct randomGenerator {
	template <std::integral T>    // Needs to be stronger as char etc are integral but not valid
	static T rGenerator(T minv, T maxv) {
		static std::mt19937 rng { std::random_device {}() };
		static std::uniform_int_distribution<T> uid(minv, maxv);

		uid.param(typename decltype(uid)::param_type { minv, maxv });
		return uid(rng);
	}
};

int main() {
	constexpr std::array names { "Bob", "Mike", "Sam", "Tom", "Dick", "Harry" };

	std::cout << "Player name is " << names[randomGenerator::rGenerator<size_t>(0, names.size() - 1)] << '\n';
}

Last edited on
I agree with you @seeplus - and as I said in my comments inside code, I could use a simple function too, but I liked this code shape :)
A static function (or template) is a good idea - clever...
This one could be more efficient according to your observation.
However I really like your template too :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <random>
#include <iostream>
#include <vector>

struct randomGenerator {

    static int rGenerator(int min, int max) 
    {
	static std::mt19937 rng{ std::random_device {}() };
	static std::uniform_int_distribution<int> uid(min, max);

	uid.param(typename decltype(uid)::param_type{ min, max });
	return uid(rng);
    }
};

int main() 
{
    std::vector<std::string> names = { "Bob", "Mike", "Sam", "Tom", "Dick", "Harry" };
    std::cout << "Player name is " << names[randomGenerator::rGenerator(0, static_cast<int>(names.size() - 1))] << std::endl;
}
Last edited on
L19 If you make the container std::array instead of std::vector then you can specify constexpr for names.
If you make the container std::array instead of std::vector then you can specify constexpr for names.

In this context, I understand that we can "close" the container using an array instead an "open" vector, but which advantage there is to use a constexpr?
Last edited on
> which advantage there is to use a constexpr?

More fundamentally, what is the advantage in making randomGenerator a Java-style class type?

It is reasonable to make it a class type if we need it to hold some state; for instance to support multiple random number engines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <concepts>
#include <random>
#include <ranges>
#include <ctime>

namespace utility
{
    template < typename T > concept num_type = std::integral<T> or std::floating_point<T> ;

    template < num_type NUM_TYPE = int, typename ENGINE = std::mt19937 > struct rand
    {
        rand( NUM_TYPE minv, NUM_TYPE maxv ) // invariant: minv <= maxv
            : rand( minv, maxv, std::random_device{}() ) {}

        template < typename... SEED >
        rand( NUM_TYPE minv, NUM_TYPE maxv, SEED&&... seed ) // invariant: minv <= maxv
            : rng( std::forward<SEED>(seed)... ), distrib(minv,maxv) {}

        NUM_TYPE operator() () { return distrib(rng) ; }

        rand& param( NUM_TYPE minv, NUM_TYPE maxv ) // invariant: minv <= maxv
        {
            distrib.param( typename distribution_type::param_type{ minv, maxv } ) ;
            return *this ;
        }

        private:
            ENGINE rng ;
            using distribution_type = std::conditional_t< std::integral<NUM_TYPE>,
                                                          std::uniform_int_distribution<NUM_TYPE>,
                                                          std::uniform_real_distribution<NUM_TYPE> >;
            distribution_type distrib ;
    };
}

int main() // usage example
{
    const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZXabcdefghijklmnopqrstuvwxyz0123456789";

    utility::rand<std::size_t> rand_i( 0, std::ranges::size(alphabet) - 1 ) ;
    for( int i = 0 ; i < 20 ; ++i ) std::cout << alphabet[ rand_i() ]  ;
    std::cout << '\n' ;

    std::cout << rand_i.param( 22222U, 33333U )() << '\n' ;

    utility::rand<double> rand_d( 1.2345, 5.6789, std::time(nullptr) ) ;
    for( int i = 10 ; i < 15 ; ++i ) std::cout << rand_d() << ' ' ;
    std::cout << '\n' ;
}

http://coliru.stacked-crooked.com/a/67b56978d1c1e4a6
Powerful - @JLBorges amazing code. I don't understand the half of its semantic, but it deserves a serious study. I appreciate this one too which I made in order to randomize an integer or a float using std::conditional. According to yours, it seems really minimalist, but it works as expected :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <random>

struct randomGenerator {

    template <typename T>
    static T rGenerator(T min, T max)
    {   // avoid a crash if min is greater than max
        if (min > max) std::swap(min, max);

        static std::random_device seeder;
        static std::mt19937 gen(seeder());

        typename std::conditional<std::is_integral<T>::value,
            std::uniform_int_distribution<T>, // for integral
            std::uniform_real_distribution<T> // for real numbers
            >::type distribution(min, max);

        return distribution(gen);
    }
};

int main()
{
    std::cout << "Random Integral -> " << randomGenerator::rGenerator(0, 10) << std::endl;
    std::cout << "Random Real -> " << randomGenerator::rGenerator(0.0f, 1.0f) << std::endl;
}



Random Integral -> 4
Random Real -> 0.22355
Last edited on
Using a static distribution in a reusable interface, changing the parameters as needed, isn't a new or revolutionary idea. It was documented at least as far back as 2013 in a C++ working paper, N3551.

https://open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3551.pdf

Look at the "What else can a distribution do?" & "A simple toolkit" sections.

Construct once, adapt and reuse multiple times as needed.

Creating a header-only or module interface library file that one can add to a project as needed leverages the power of reuse, the "simple toolkit" section is a bare-bones framework. The interface can be crafted so generating random numbers with <random> is as easy as using C's srand/rand.
Thank you for the documentation. Printed - I will read it soon ++
My adaptation of that working paper's simple toolkit proposal. Header-only and C++20 module interface files. Along with sample test chassis code to test the library.

https://github.com/GeorgePimpleton/misc_files/tree/main/Random%20Toolkit
Topic archived. No new replies allowed.