web-dev-qa-db-ja.com

<random>を使用してRand()を置き換える方法は?

C++ 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()を置き換えるのに適度に簡単な方法はありますか?

30
Dietmar Kühl

Srand()とRand()を置き換える簡単な方法はありますか?

完全な開示:Rand()は好きではありません。それは悪いことであり、非常に簡単に乱用されます。

C++ 11ランダムライブラリは、長い間欠けていたボイドを埋めます。高品質のランダムライブラリの問題は、使用が難しいことが多いことです。 C++ 11 <random>ライブラリは、この点で大きな前進を表しています。数行のコードと私は、非常にうまく動作し、多くの異なる分布からランダムな変量を簡単に生成する非常に良いジェネレーターを持っています。


上記を考えると、あなたへの私の答えは少し異端です。 Rand()で十分な場合は、それを使用してください。 Rand()と同じくらい悪い(そして悪い)ので、これを削除すると、C言語で大きな問題が発生します。 Rand()の悪さが本当にあなたのニーズに十分満足できるものであることを確認してください。

C++ 14はRand()を廃止しませんでした。 Rand()を使用するC++ライブラリの非推奨の関数のみです。 C++ 17はRand()を廃止する可能性がありますが、削除されません。つまり、Rand()が消えるまでにはさらに数年かかります。 C++委員会が最終的にC++標準ライブラリからRand()を削除するまでに、退職するか別の言語に切り替える可能性は高いです。

std::vector<int> v(size); std::generate(v.begin(), v.end(), std::Rand);の行に沿って何かを使用して、std :: sort()のさまざまな実装をベンチマークするためにランダム入力を作成しています

暗号的に安全なPRNGは必要ありません。MersenneTwisterも必要ありません。この特定のケースでは、おそらくRand()で十分です。


更新
C++ 11ランダムライブラリstd::minstd_Randには、Rand()srand()に代わる素敵な単純な置換があります。

#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()()std::uint_fast32_tを返します。ただし、アルゴリズムは結果を1〜2に制限します31-2、包括的。つまり、結果は常にstd::int_fast32_t(またはintが32ビット以上の場合はintに)に安全に変換されます。

16
David Hammen

randutilspcg-random.org のMelissa O'Neillはどうですか?

導入ブログの投稿 から:

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";
6
jtbandes

Cスタイルの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>関数とまったく同じように使用できます。引数をとらず、std::random_deviceから取得した「実際の」乱数をエンジンにシードするsrandオーバーロードを追加しました。もちろん、そのランダム性がどの程度「本物」であるかは、実装によって定義されます。

エンジンとディストリビューションはthread_localstaticインスタンスとして保持されるため、複数の呼び出しにわたって状態を伝達しますが、異なるスレッドが予測可能なシーケンスを監視できるようにします。 (エンジンを再構築したり、ロックを使用したり、他の人の現金を無駄にする可能性がないため、パフォーマンスも向上します。)

std::default_random_engineを使ったのはあなたが使ったからですが、あまり好きではありません。メルセンヌツイスターエンジン(std::mt19937およびstd::mt19937_64)は、はるかに優れた「ランダムさ」を生み出し、驚くべきことに より高速であることが確認されています 。準拠しているプログラムは、特定の種類の疑似ランダムエンジンを使用して実装されているstd::Randに依存する必要があるとは思いません。 (そうであったとしても、実装はstd::default_random_engineを自由に定義できるため、確実にstd::minstd_Randのようなものを使用する必要があります。)

6
5gon12eder

エンジンが直接値を返すという事実を悪用する

_<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;
}
_

ただし、すべてのエンジンはunsigned整数型の値を返すことに注意してください。つまり、signed積分がオーバーフローする可能性があります(これはundefined-behaviorにつながります)。

新しい値を取得するすべての場所でunsigned値を使用することに問題がない場合、上記は_std::srand_ + _std::Rand_の使用を置き換える簡単な方法です。

:上記の説明を使用すると、の_result_type_が原因で、一部の値が他の値よりも返される可能性が高くなる可能性がありますengine宛先タイプに格納できる最大値の偶数倍の最大値がない。 
これまでにこれまで心配していなかった場合-Rand()%low+highのようなものを使用する場合-今は心配する必要はありません。

:_std::engine-type::result_type_が少なくとも希望する値の範囲と同じであることを確認する必要があります(_std::mt19937::result_type_は_uint_fast32_t_)。


エンジンを一度だけシードする必要がある場合

最初にdefault-construct_std::default_random_engine_(これは、によって選択されたいくつかのエンジンのtypedefである必要はありません) implementation)、そして後でそれにシードを割り当てる;これは、random-engineの適切なコンストラクタを使用して、一度に行うことができます。

_std::random-engine-type engine (seed);
_

ただし、エンジンを再シードする必要がある場合は、_std::random-engine::seed_を使用する方法です。


他のすべてが失敗した場合; ヘルパー関数を作成する

あなたが投稿したコードが少し複雑に見えても、それを書くのは一度だけです。

copy + pasteコード内のいくつかの場所に書き込んだものだけに誘惑される状況に陥った場合は、いつもcopy + pasting;ヘルパー関数を導入します。

_Intentionally left blank, see other posts for example implementations._

次のような単純な関数を作成できます。

#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);

前述のとおり ここ

0
UpmostScarab