#include <iostream>
#include <memory>
#include <numeric>
#include <random>
#include <unordered_map>
int main() {
constexpr std::size_t min = 1000;
constexpr std::size_t max = 9999;
constexpr std::size_t size = 10;
static_assert(max - min > size, "");
// Create a new mersenne twister PRNG and seed it with a random 32bit value.
std::random_device device;
std::mt19937 engine{device()};
// Allocate an array on the heap large enough for our output and fill it with
// the range [min .. min + size).
std::unique_ptr<std::size_t[]> array{new std::size_t[size]};
std::iota(array.get(), array.get() + size, min);
// Create a new empty hashtable mapping size_t to size_t and ask the hashtable
// to resize itself it accomidate the maximum number of elements we will be
// inserting. The reserve is only an optimization that can be commented out.
std::unordered_map<std::size_t, std::size_t> map;
map.reserve(max - min - size + 1);
// We use the above array and hashtable to represent a sparse sequence of all
// the values in the range [0 .. max-min]. Indexes [0 .. size) are stored in
// the array and [size .. max-min] are stored in the hashtable and lazily
// initialized.
for (std::size_t i = 0; i != size; ++i) {
// Create a new uniform distribution for the range [i, max - min] and ask it
// to generate a single number using our PRNG.
std::uniform_int_distribution<std::size_t> dist{i, max - min};
auto index = dist(engine);
// std::swap(array[i], index < size
// ? array[index]
// : map.insert({index, min + index}).first->second);
// I've broken this statement into several lines to better explain.
// Pointer to the element we will be swapping with.
std::size_t *target;
// Check if the index is in the array or the the hashtable
if (index < size) {
// The index is in the array.
// Save the pointer to the value stored in the array at this index.
target = &array[index];
} else {
// The index is in the hashtable.
// If the key index does not exist yet, then insert value min + index for
// this index. If the key already exists then this does no modifications.
// The result of this operation is a pair of {iterator, bool} where bool
// tells us if the insert was successful or it already existed, but we
// dont care about this. The iterator points to the {key, value} pair that
// is stored in the hashtable after the insert.
auto pair = map.insert({index, min + index});
// Save the pointer to the value stored in the hashtable at this index.
target = &pair.first->second;
}
// Swap the ith element of the array with the target element.
std::swap(array[i], *target);
}
// Print the array separated by commas.
if (size != 0) {
std::cout << array[0];
for (size_t i = 1; i != size; ++i) std::cout << ", " << array[i];
std::cout << "\n";
}
}