Is there any way you can have a 4 digit number without repetition - e.g. not 1130
but 1234
? I read std::random_shuffle
could do this b
If you can count how many valid (i.e. acceptable) sequences exist, and you can devise a bijective function that maps from this counter to each valid sequence instance then things become trivial.
If I understand your example correctly, you want to generate a random 4 digit sequence ABCD (representing an integer in the range [0,9999]) where digits A, B, C and D are different from one another.
There are 5040 such valid sequences: 10 * 9 * 8 * 7.
Given any integer in the range [0, 5039], the following function will return a valid sequence (i.e. one in which each digit is unique), represented as an integer:
int counter2sequence(int u) {
int m = u/504;
u %= 504;
int h = u/56;
u %= 56;
int t = u/7;
u %= 7;
const int ih = h;
const int it = t;
if (ih >= m) ++h;
if (it >= ih) ++t;
if (t >= m) ++t;
if (u >= it) ++u;
if (u >= ih) ++u;
if (u >= m) ++u;
return ((m*10 + h)*10 + t)*10 + u;
}
E.g.
counter2sequence(0) => 0123
counter2sequence(5039) => 9876
Another method, using only standard data and numeric algorithms.
#include <random>
#include <array>
#include <iostream>
#include <numeric>
template<class Engine>
int unrepeated_digits(int ndigits, Engine &eng) {
std::array<int, 10> digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto add_digit = [](auto x, auto digit) {
return x * 10 + digit;
};
std::shuffle(std::begin(digits), std::end(digits), eng);
return std::accumulate(std::begin(digits), std::next(std::begin(digits), ndigits), 0, add_digit);
}
int main() {
std::random_device rnd;
std::default_random_engine eng(rnd());
for (int i = 0; i < 10; ++i)
std::cout << unrepeated_digits(4, eng) << std::endl;
}
example output:
7623
3860
9563
9150
3219
8652
4789
2457
1826
9745
I don't see a problem with std::random_shuffle
:
#include <iostream>
#include <string>
#include <algorithm>
#include <random>
#include <chrono>
int main()
{
std::string s;
std::generate_n(std::back_inserter(s), 10,
[]() { static char c = '0'; return c++; });
// s is now "0123456789"
std::mt19937 gen(std::random_device{}());
// if 0 can't be the first digit
std::uniform_int_distribution<size_t> dist(1, 9);
std::swap(s[0], s[dist(gen)]);
// shuffle the remaining range
std::shuffle(s.begin() + 1, s.end(), gen); // non-deprecated version
// convert only first four
auto x = std::stoul(s.substr(0, 4));
std::cout << x << std::endl;
}
Live on Coliru
One possibility is to generate a string containing the digits, and to use the C++14 function std::experimental::sample()
#include <iostream>
#include <random>
#include <string>
#include <iterator>
#include <experimental/algorithm>
int main() {
std::string in = "0123456789", out;
do {
out="";
std::experimental::sample(in.begin(), in.end(), std::back_inserter(out), 4, std::mt19937{std::random_device{}()});
std::shuffle(out.begin(), out.end(), std::mt19937{std::random_device{}()});
} while (out[0]=='0');
std::cout << "random four-digit number with unique digits:" << out << '\n';
}
Edit:
Changed to prevent a result that starts with a 0. Hat tip to @Bathsheba who indicated that this could be a problem.
I think you need to generate each digit separately. For example you have array from 0 to 9 with 0..9 digits. For first digit you generate number from 0 to 9 and pick up digit from this array. Then you swap this array element t with last element of array. For second digit you generate number form 0 to 8. And so on.