Подтвердить что ты не робот

Как использовать <random> для замены rand()?

С++ 11 представил заголовок <random> с объявлениями для двигателей случайных чисел и случайных распределений. Это прекрасное время для замены тех видов использования rand(), которое часто бывает проблематичным по-разному. Однако кажется очевидным, как заменить

srand(n);
// ...
int r = rand();

Основываясь на объявлениях, кажется, что равномерное распределение может быть построено примерно так:

std::default_random_engine engine;
engine.seed(n);
std::uniform_int_distribution<> distribution;
auto rand = [&](){ return distribution(engine); }

Этот подход кажется довольно вовлеченным и, безусловно, я не буду помнить, в отличие от использования srand() и rand(). Я знаю N4531, но даже это все еще кажется довольно привлекательным.

Существует ли простой способ заменить srand() и rand()?

4b9b3361

Ответ 1

Есть ли простой способ заменить srand() и rand()?

Полное раскрытие: мне не нравится rand(). Это плохо, и это очень легко злоупотребляет.

Случайная библиотека С++ 11 заполняет пустоту, которой не хватало долгое время. Проблема с высококачественными случайными библиотеками заключается в том, что их часто трудно использовать. Библиотека С++ 11 <random> представляет огромный шаг вперед в этом отношении. Несколько строк кода, и у меня очень хороший генератор, который ведет себя очень хорошо и легко генерирует случайные вариации из множества разных дистрибутивов.


Учитывая вышеизложенное, мой ответ вам немного еретический. Если rand() достаточно хорош для ваших нужд, используйте его. Столь же плохо, как rand() (и это плохо), удаление его будет представлять собой огромный разрыв с языком C. Просто убедитесь, что плохое качество rand() действительно достаточно для ваших нужд.

С++ 14 не осуждал rand(); это только устаревшие функции в библиотеке С++, которые используют rand(). Хотя С++ 17 может обесценить rand(), он не удалит его. Это означает, что у вас есть еще несколько лет, прежде чем rand() исчезнет. Вероятность того, что вы уйдете на пенсию или переключитесь на другой язык к тому моменту, когда комитет С++ окончательно удалит rand() из стандартной библиотеки С++.

Я создаю случайные входы для сравнения различных реализаций std:: sort(), используя что-то вдоль строк std::vector<int> v(size); std::generate(v.begin(), v.end(), std::rand);

Для этого вам не нужен криптографически безопасный PRNG. Тебе даже не нужна Мерсенн Твистер. В этом конкретном случае rand(), вероятно, достаточно хорош для ваших нужд.


Обновление
Существует хорошая простая замена для rand() и srand() в случайной библиотеке С++ 11: std::minstd_rand.

#include <random>
#include <iostream>

int main ()
{
    std:: minstd_rand simple_rand;

    // Use simple_rand.seed() instead of srand():
    simple_rand.seed(42);

    // Use simple_rand() instead of rand():
    for (int ii = 0; ii < 10; ++ii)
    {
        std::cout << simple_rand() << '\n';
    }
}

Функция std::minstd_rand::operator()() возвращает a std::uint_fast32_t. Однако алгоритм ограничивает результат от 1 до 2 31 -2 включительно. Это означает, что результат всегда будет безопасно преобразовывать в std::int_fast32_t (или в int, если int имеет длину не менее 32 бит).

Ответ 2

Как насчет randutils Мелиссы О'Нил из pcg-random.org?

Из вводного сообщения в блоге:

randutils::mt19937_rng rng;

std::cout << "Greetings from Office #" << rng.uniform(1,17)
          << " (where we think PI = "  << rng.uniform(3.1,3.2) << ")\n\n"
          << "Our office morale is "   << rng.uniform('A','D') << " grade\n";

Ответ 3

Предполагая, что вам нужны функции C-style rand и srand, в том числе их причудливость, но с хорошим случайным, это самое близкое, что я мог бы получить.

#include <random>
#include <cstdlib>  // RAND_MAX  (might be removed soon?)
#include <climits>  // INT_MAX   (use as replacement?)


namespace replacement
{

  constexpr int rand_max {
#ifdef RAND_MAX
      RAND_MAX
#else
      INT_MAX
#endif
  };

  namespace detail
  {

    inline std::default_random_engine&
    get_engine() noexcept
    {
      // Seeding with 1 is silly, but required behavior
      static thread_local auto rndeng = std::default_random_engine(1);
      return rndeng;
    }

    inline std::uniform_int_distribution<int>&
    get_distribution() noexcept
    {
      static thread_local auto rnddst = std::uniform_int_distribution<int> {0, rand_max};
      return rnddst;
    }

  }  // namespace detail

  inline int
  rand() noexcept
  {
    return detail::get_distribution()(detail::get_engine());
  }

  inline void
  srand(const unsigned seed) noexcept
  {
    detail::get_engine().seed(seed);
    detail::get_distribution().reset();
  }

  inline void
  srand()
  {
    std::random_device rnddev {};
    srand(rnddev());
  }

}  // namespace replacement

Функции replacement::* могут использоваться точно так же, как их std::* аналоги из <cstdlib>. Я добавил перегрузку srand, которая не принимает никаких аргументов и засевает двигатель с "реальным" случайным числом, полученным с помощью std::random_device. Как "реальная" эта случайность будет, конечно, определена.

Двигатель и распределение хранятся как thread_local static экземпляры, поэтому они переносят состояние на несколько вызовов, но все же позволяют различным потокам наблюдать предсказуемые последовательности. (Это также повышает производительность, потому что вам не нужно перестраивать двигатель или использовать блокировки и потенциально уничтожать другие жертвы.)

Я использовал std::default_random_engine, потому что вы это сделали, но мне это не очень нравится. Двигатели Mersenne Twister (std::mt19937 и std::mt19937_64) дают намного лучшую "случайность", и, что удивительно, также наблюдалось быстрее. Я не думаю, что любая совместимая программа должна полагаться на std::rand, реализованную с использованием любого конкретного псевдослучайного движка. (И даже если бы это было так, реализации могут определять std::default_random_engine как угодно, поэтому вам нужно будет использовать что-то вроде std::minstd_rand, чтобы быть уверенным.)

Ответ 4

Нарушение того факта, что двигатели напрямую возвращают значения

Все двигатели, определенные в <random>, имеют operator()(), которые могут использоваться для получения следующего сгенерированного значения, а также для продвижения внутреннего состояния двигателя.

std::mt19937 rand (seed); // or an engine of your choosing
for (int i = 0; i < 10; ++i) {
  unsigned int x = rand ();
  std::cout << x << std::endl;
}

Следует, однако, отметить, что все двигатели возвращают значение некоторого неподписанного интегрального типа, что означает, что они могут потенциально переполнить подписанный интеграл (который затем приведет к undefined -behavior).

Если вам хорошо, когда вы используете неподписанные значения везде, где вы извлекаете новое значение, приведенное выше является простым способом заменить использование std::srand + std::rand.

Примечание. Использование того, что было описано выше, может привести к некоторым значениям, имеющим более высокую вероятность возврата, чем другие, из-за того, что result_type engine не имеет максимальное значение, которое является кратным наивысшему значению, которое может быть сохранено в типе назначения.
Если вы не беспокоились об этом в прошлом — при использовании чего-то вроде rand()%low+high — вы не должны беспокоиться об этом сейчас.

Примечание. Вам нужно убедиться, что std::engine-type::result_type не меньше, чем ваш желаемый диапазон значений (std::mt19937::result_type is uint_fast32_t).


Если вам нужно только разгрузить двигатель

Нет необходимости сначала строить по умолчанию a std::default_random_engine (это всего лишь typedef для некоторого движка, выбранного реализацией), а затем назначая ему семя; это можно сделать все сразу, используя соответствующий конструктор случайного движка.

std::random-engine-type engine (seed);

Если вам нужно снова заново запустить двигатель, использование std::random-engine::seed - это способ сделать это.


Если все остальное не работает; создать вспомогательную функцию

Даже если код, который вы опубликовали, выглядит несколько сложным, вы должны только написать его один раз.

Если вы оказались в ситуации, когда у вас возникли соблазны просто скопировать + вставить то, что вы написали в несколько мест в вашем коде, рекомендуется, как всегда, при копировании и вставке; ввести вспомогательную функцию.

Intentionally left blank, see other posts for example implementations.

Ответ 5

Вы можете создать простую функцию, например:

#include <random>
#include <iostream>
int modernRand(int n) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, n);
    return dis(gen);
}

И позже используйте его вот так:

int myRandValue = modernRand(n);

Как упоминалось здесь