#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#else
double GetTickCount()
{
return clock() * 1000.0 / CLOCKS_PER_SEC;
}
#endif
#include <vector>
#include <set>
#include <algorithm>
#if defined(_MSC_VER) && !defined(__MWERKS__) && !defined(for) && _MSC_VER <= 1200
#define for if(0) {} else for
#endif // _MSC_VER && !__MWERKS__ && !for && _MSC_VER <= 1200
// msvc 'for' scoping hack
static int n_Rand(int n_max)
{
int n = int(rand() / (double(RAND_MAX) + 1) * (n_max + 1));
assert(n >= 0 && n <= n_max);
// get a random number
return n;
}
static void Print(int i)
{
printf("%d ", i);
}
struct Dec {
void operator ()(int &r_n) const
{
-- r_n;
}
};
template <class CScalar>
struct TNeedle {
CScalar n;
TNeedle(const CScalar &r_n)
:n(r_n)
{}
};
template <class CScalar>
class CCompareWithOffset {
protected:
typename std::vector<CScalar>::iterator m_p_begin_it; /**< @brief iterator pointing to the first element of the vector */
public:
CCompareWithOffset(typename std::vector<CScalar>::iterator p_begin_it)
:m_p_begin_it(p_begin_it)
{}
inline bool operator ()(const CScalar &r_value, TNeedle<CScalar> n) const
{
size_t n_index = &r_value - &*m_p_begin_it;
// calculate index in the array
return r_value < n.n + n_index;
}
inline bool operator ()(TNeedle<CScalar> n, const CScalar &r_value) const
{
size_t n_index = &r_value - &*m_p_begin_it;
// calculate index in the array
return n.n + n_index < r_value;
}
};
namespace std {
template <class RanIt, class V>
void iota(RanIt b, RanIt e, V counter)
{
if(b >= e)
return;
for(; b != e; ++ b, ++ counter)
*b = counter;
}
}
// inject iota for old tyme people
static void RandomUniqueSequence(std::vector<int> &rand_num, const size_t n_number_num, const size_t n_item_num)
{
assert(n_number_num <= n_item_num);
rand_num.clear(); // !!
#if 0
// b1: 20250.000 msec
// b2: 2296.000 msec
std::set<int> numbers;
while(numbers.size() < n_number_num)
numbers.insert(n_Rand(n_item_num - 1)); // might have duplicates here
// generate unique random numbers
rand_num.resize(numbers.size());
std::copy(numbers.begin(), numbers.end(), rand_num.begin());
// copy the numbers from a set to a list
#elif 0
// b1: 519515.000 msec
// b2: 20312.000 msec
std::vector<int> all_numbers(n_item_num);
std::iota(all_numbers.begin(), all_numbers.end(), 0);
// generate all the numbers
for(size_t i = 0; i < n_number_num; ++ i) {
assert(all_numbers.size() == n_item_num - i);
int n = n_Rand(n_item_num - i - 1);
// get a random number
rand_num.push_back(all_numbers[n]); // put it in the output list
all_numbers.erase(all_numbers.begin() + n); // erase it from the input
}
// generate random numbers
#elif 0
// b1: 2391.000 msec | 3187.000 msec (the fastest)
// b2: 51484.000 msec | 3734.000 msec
// b3: 5000.000 msec (10), 1172.000 msec (20), 3906.000 msec (50), 6938.000 msec (75), 10984.000 msec (100)
for(size_t i = 0; i < n_number_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
// get a random number
size_t n_where = i;
for(size_t j = 0; j < i; ++ j) {
if(n >= rand_num[j])
++ n;
else {
n_where = j;
break;
}
}
// see where it should be inserted
rand_num.insert(rand_num.begin() + n_where, 1, n);
// insert it in the list, maintain a sorted sequence
}
// tier 0 - naive algorithm
#elif 0
// b1: 2391.000 msec | 3312.000 msec (the fastest)
// b2: 51406.000 msec | 4218.000 msec
for(size_t i = 0; i < n_number_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
// get a random number
size_t n_where = i;
for(size_t j = 0; j < i; ++ j) {
if(n + j < rand_num[j]) {
n_where = j;
break;
}
}
// see where it should be inserted
rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);
// insert it in the list, maintain a sorted sequence
}
// tier 1 - use comparison with offset instead of increment
#elif 0
// b1: 3047.000 msec | 3750.000 msec
// b2: 99375.000 msec | 5828.000 msec
//rand_num.reserve(n_number_num); // makes it slightly slower
for(size_t i = 0; i < n_number_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
// get a random number
size_t n_where = i;
for(size_t j = 0; j < i; ++ j) {
if(n /*+ j*/ < rand_num[j] /*+ j*/) {
n_where = j;
break;
}
}
// see where it should be inserted
rand_num.insert(rand_num.begin() + n_where, 1, n); // each number encoded as value + its index
// insert it in the list, maintain a sorted sequence
for(size_t j = n_where + 1; j <= i; ++ j)
-- rand_num[j];
// compensate for insertion in the middle
}
for(size_t i = 1; i < n_number_num; ++ i)
rand_num[i] += i; // convert value + its index to absolute values
// tier 2 - use offset encoding
#elif 0
// b1: 4078.000 msec | 3781.000 msec
// b2: 33812.000 msec | 3203.000 msec
rand_num.reserve(n_number_num); // don't want insertion invalidating the iterators
for(size_t i = 0; i < n_number_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
// get a random number
std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(), n);
// see where it should be inserted
rand_num.insert(p_where_it, 1, n); // each number encoded as value + its index
assert(*p_where_it == n); // make sure the iterator was not invalidated (if it were, this would potentially trigger a different assertion in the iterator dereference, or crash the program depending on the STL implementation)
// insert it in the list, maintain a sorted sequence
//struct Dec { void operator ()(int &r_n) const { -- r_n; } }; // above
std::for_each(++ p_where_it, rand_num.end(), Dec());
// compensate for insertion in the middle
}
for(size_t i = 1; i < n_number_num; ++ i)
rand_num[i] += i; // convert value + its index to absolute values
// tier 3 - use binary search with offset encoding
#elif 1
// b1: 4063.000 msec | 3578.000
// b2: 12907.000 msec | 1703.000 msec (the fastest)
// b3: 5656.000 msec (10), 1344.000 msec (20), 4172.000 (50), 6969.000 msec (75), 10094.000 msec (100)
for(size_t i = 0; i < n_number_num; ++ i) {
int n = n_Rand(n_item_num - i - 1);
// get a random number
std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),
TNeedle<int>(n), CCompareWithOffset<int>(rand_num.begin()));
// see where it should be inserted
rand_num.insert(p_where_it, 1, n + (p_where_it - rand_num.begin())); // msvc goes crazy without the parens
// insert it in the list, maintain a sorted sequence
}
// tier 4 - use binary search
// this does binary search with the comparison "n + j < rand_num[j]" with non-constant needle,
// which can be rewritten to one with constant needle and a modified sequence "n < rand_num[j] - j".
// it is easily shown that since the lowest distance between two elements of the original
// sequence "rand_num[j]" is:
//
// rand_num[j] - rand_num[j - 1] >= 1
//
// (the generated numbers are unique), and at the same time, the difference
// between different indices of "-j" is:
//
// -j - -(j - 1) = -1
//
// The difference between elements of their sum "rand_num[j] - j" is:
//
// (rand_num[j] - j) - (rand_num[j - 1] - (j - 1)) >= 0
//
// therefore, the sequence is nondecreasing and this comparison can be used.
#endif // 0
}
int main(int n_arg_num, const char **p_arg_list)
{
srand(int(time(0)));
const size_t n_number_num = 3;
const size_t n_item_num = 7;
std::vector<int> rand_num;
std::vector<size_t> histogram(n_item_num);
for(size_t n_pass = 0; n_pass < 10000; ++ n_pass) {
RandomUniqueSequence(rand_num, n_number_num, n_item_num);
assert(rand_num.size() == n_number_num); // make sure we generated all the numbers
for(size_t i = 0, n = rand_num.size(); i < n; ++ i)
assert(rand_num[i] >= 0 && size_t(rand_num[i]) < n_item_num); // make sure the numbers are in range
std::sort(rand_num.begin(), rand_num.end());
assert(std::unique(rand_num.begin(), rand_num.end()) == rand_num.end()); // make sure the numbers are unique
// debug checks
for(size_t i = 0, n = rand_num.size(); i < n; ++ i)
++ histogram[rand_num[i]];
// accumulate histograms to see the distribution
}
printf("histogram over many iterations:\n");
std::for_each(histogram.begin(), histogram.end(), &Print);
printf("\n");
double t = GetTickCount() * 1e-3;
/*for(size_t n_pass = 0; n_pass < 10000000; ++ n_pass) {
const size_t n_number_num = 7;
const size_t n_item_num = 5000;
RandomUniqueSequence(rand_num, n_number_num, n_item_num);
}*/
// benchmark 1
for(size_t n_pass = 0; n_pass < 10000; ++ n_pass) {
const size_t n_number_num = 700;
const size_t n_item_num = 5000;
RandomUniqueSequence(rand_num, n_number_num, n_item_num);
}
// benchmark 2
/*for(size_t n_pass = 0; n_pass < 1000000; ++ n_pass) {
const size_t n_number_num = 75;
const size_t n_item_num = 1000;
RandomUniqueSequence(rand_num, n_number_num, n_item_num);
}*/
// benchmark 3
t = GetTickCount() * 1e-3 - t;
printf("done. it took %.3f msec\n", t * 1000);
return 0;
}