#c #c 11 #random
#c #c 11 #Случайный
Вопрос:
Для моей программы до сих пор мне нужно было время от времени рисовать одно случайное значение в [0 .. k[, и использование функций C 11 <random>
работает очень хорошо. Мой текущий код выглядит примерно так
class Random
{
public:
Random() : rng( rd() ) { }
inline int getRandNum( int limit ) { return ( numbers(rng) % limit ); }
private:
std::random_device rd;
std::mt19937 rng;
std::uniform_int_distribution<int> numbers;
};
Теперь мне нужно нарисовать подряд n разных значений в [0 ..k[. Я искал что-то в <random>
разрешении этого, но либо я не могу это найти, либо такой вещи еще не существует. Есть ли умный, более элегантный способ продолжить, чем вызвать мою функцию getRandNum и повторять, пока я не получу n разных значений?
РЕДАКТИРОВАТЬ: чтобы дать представление, в моей программе k — это несколько тысяч, а n — несколько десятков.
Комментарии:
1. Насколько велик диапазон 0 .. k и насколько велик ‘n’? Ответы на эти вопросы определят наилучшее решение.
2. С массивом
[0..k[
вы можете использоватьstd::shuffle
3. Если ‘n’ мало по сравнению с размером диапазона 0.. k, то функция draw-and-discard использует меньший объем памяти для решения проблемы за счет вычислительных затрат на удаление и повторное рисование из-за нескольких коллизий. Если n приблизится к размеру диапазона, коллизии станут чрезмерно распространенными, но экономия памяти при извлечении и отбрасывании будет несущественной, поскольку отслеживание розыгрышей будет потреблять почти тот же объем памяти, что и метод генерации и перемешивания. В таком случае оптимальным является генерация и перетасовка.
4. Обратите внимание, что ваш текущий код ошибочен: у вас, вероятно, есть шаг где-то в дистрибутиве. Вы должны создать распределение в
getRandNum
функции, передаваемой(0, k-1)
конструктору, и избегать изменения размера пользовательского диапазона.5. @FlorianRichoux: в этом случае нет смысла иметь экземпляр
std::uniform_int_distribution
; единственный интерес экземпляра (в целом) заключается в обеспечении правильного распределения, если вы делаете это вручную на стороне, вы можете так же легко создавать новый экземпляр каждый раз. И если вы хотите правильно использовать дистрибутив, то вам нужно уточнить его диапазон при создании (вы могли бы уточнить диапазон,0, k*l*m
гдеk
,l
иm
— это ограничения, которые вы собираетесь использовать позже). Кстати, я хотел бы отметить, что%
для интегралов, пожалуй, одна из самых медленных операций процессора.
Ответ №1:
Это решение не зависит от C , но может быть легко реализовано на любом языке.
По сути, вы хотите перетасовать числа от 0 до k и выбрать первые n чисел, где n <= k. Это можно сделать с помощью алгоритма выборки из резервуара. Смотрите эту ссылку на Википедию для псевдокода.
Обратите внимание, что можно получить n чисел, не сохраняя все k чисел и не перетасовывая их. То есть можно просто использовать O (n) пробел, где n — количество случайных чисел, которые вы хотите получить, вместо O (k). Временная сложность для этого алгоритма равна O (k), если предположить, что генерация случайного числа занимает O (1) времени.
Комментарии:
1. Следует отметить, что таким образом вы получаете перестановку чисел, где каждое число появляется ровно один раз. Иногда это может быть проблемой.
2. @FreeNickname. Вы правы, но я считаю, что это то, чего хочет op. Он заявил, что хочет «нарисовать n разных значений в [0, k]», если я не неправильно понял, что вы имели в виду.
3.да, вы правы. Я был смущен
<random>
. И на самом деле, если это так, то вызов<random>
n
раз на самом деле не является правильным решением, поскольку он может выдавать одно и то же число более одного раза.4. Да, я хочу n разных значений, поэтому случайная перестановка [0 .. k[ и затем взятие n первого числа — это решение моей проблемы. Меня беспокоит то, что это действительно время O (k), и мне скорее нравится что-то в O (n). Меня не волнует сложность пространства.
5. Я имею в виду, меня волнует скорость, а не пространство, поэтому что-то во времени O (n) было бы здорово.
Ответ №2:
Если k равно нескольким тысячам, а n — десяткам, то генерация перестановок действительно не лучший выбор. Но вызов getRandNum — это тоже не то, что вы хотите, потому что он может возвращать одно и то же значение несколько раз. Один из вариантов — сгенерировать случайную последовательность сразу, проверяя, чтобы числа не повторялись. Самый простой (и, возможно, даже самый эффективный) способ добиться этого — использовать set
.
Вот так:
#include <vector>
#include <set>
#include <iostream>
#include <random>
class Random
{
public:
Random() : rng( rd() ) { }
inline int getRandNum( int limit ) { return ( numbers(rng) % limit ); }
std::set<int> getRandSequence(int limit, int n);
private:
std::random_device rd;
std::mt19937 rng;
std::uniform_int_distribution<int> numbers;
};
std::set<int> Random::getRandSequence(int limit, int n)
{
std::set<int> generatedSequence;
while (generatedSequence.size() < n) //size() for set is O(1) if I'm not mistaken
generatedSequence.insert(getRandNum(limit));
return generatedSequence;
}
int main()
{
Random r;
auto sequence = r.getRandSequence(1000, 10);
std::cout << "Seq;uence: " << std::endl;
for (int number : sequence)
std::cout << number << std::endl;
std::cout << "End" << std::endl;
return 0;
}
Кстати, random_device
создание обходится дорого, а uniform_int_distribution
создание, насколько я помню, нет. Так что это может быть еще более эффективным:
std::set<int> Random::getRandSequence(int limit, int n)
{
std::uniform_int_distribution<int> uiniformDistribution(0, limit);
std::set<int> generatedSequence;
while (generatedSequence.size() < n)
generatedSequence.insert(uiniformDistribution(rng));
return generatedSequence;
}
Кроме того, когда вы получаете равномерное распределение, а затем применяете % limit
к нему, вы больше не получаете равномерное распределение.
Ответ №3:
std::random_device rd; // obtain a random number from hardware
std::mt19937 eng(rd()); // seed the generator
std::uniform_int_distribution<> distr(0, 1500); // define the range
for(int a=0; a<limit; a ){
cout << distr(eng); //draw random nubmer
Комментарии:
1.OP хочет иметь
limit
разные числа. Что не относится к вашему решению.