チュートリアル:Randomクラス
このチュートリアルでは、Randomクラスを使用した乱数生成を紹介します。Random数はゲーム、暗号、オーディオなど、あらゆる場面で役立ちます。
レベル: 初級
プラットフォーム: Windows, macOS, Linux, iOS, Android
クラス: Random, Range, BigInteger, Colour
はじめに
このチュートリアルのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。
この手順についてサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucerを始めようを参照してください。
デモプロジェクト
デモプロジェクトは、さまざまな操作の結果を表示できるシンプルなテキストコンソールを設定しています。これにより、このチュートリアルでRandomクラスのいくつかの機能を実証できます。
デフォルト設定では、デモプロジェクトは100個のランダムに生成された32ビット整数の系列を表示します。これらの値は、-2,147,483,648 .. 2,147,483,647の範囲でほぼ均等に分布するはずです。もちろん、これらの値は本当の意味でランダムではありません。コンピュータによって生成されるため、これらは_擬似乱数_です。ほとんどの目的ではRandomオブジェクトで十分にランダムですが、特定の重要なアプリケーションや専門的なアプリケーションでは、より洗練された技術を使用する必要があるかもしれません。
テストベッドコード
このチュートリアル全体を通して、Randomクラスのさまざまな機能を実証するためにデモプロジェクトの一部を変更します。議論中のすべてのコードはMainComponentクラス内にあります。実際、最初のインスタンスでの変更の多くはrunExample()関数に対するものです。
void runExample()
{
logMessage ("------------------------- START --------------------------");
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomInt = juce::Random::getSystemRandom().nextInt();
logMessage (juce::String (randomInt));
}
logMessage ("----------------------- FINISHED -------------------------");
}
ここでは、Random::getSystemRandom()関数を使用して共有(グローバル)Randomオブジェクトにアクセスしています。このシステムRandomオブジェクトでRandom::nextInt()関数を呼び出して乱数を生成します。ほとんどの場合、すべての乱数を生成するためにこのシステムランダムオブジェクトを使用することで十分です。場合によっては、Randomクラスの独自のインスタンスを作成する必要があります。これは通常、別のスレッドで値を生成している場合です --- チュートリアル:ホワイトノイズジェネレータを構築するを参照してください。2つ(またはそれ以上)のスレッドがシステムRandomオブジェクトにアクセスしようとすると、シーケンスが破損し、問題を引き起こす可能性があります。
範囲の制限
Randomクラスによって生成される値の範囲を制限したい場合がほとんどです。これは単純な算術演算で行うこともできますが、Randomクラスはさらに簡単にしてくれます。
最大値の設定
最大値を設定するには、Random::nextInt()に整数を次のように渡すだけです:
auto randomInt = juce::Random::getSystemRandom().nextInt (6);
これは最大値を渡された引数より1少ない値に設定します。この場合、値は0 .. 5の範囲になります。この場合、引数を可能な結果の数として考える方が簡単かもしれません。最大値として6を使用すると、6つの可能な結果があることを意味します。これはサイコロを振るのと同じですが、サイコロの面は1 .. 6ではなく0 .. 5に番号付けされています!
最小値と最大値の設定
もちろん、1 .. 6の範囲の値を生成するには、次のように1を加えることができます:
auto randomInt = juce::Random::getSystemRandom().nextInt (6) + 1;
しかし、RandomクラスはRangeオブジェクトを渡すことでこれを処理できます:
auto randomInt = juce::Random::getSystemRandom().nextInt (juce::Range<int> (1, 7));
最大値は、Randomオブジェクトが生成できる最大値より1大きくする必要があります。最小値は_包含的_で、最大値は_排他的_です。
その他の数値型
上記の例では、生成された値はすべて32ビット整数の範囲内にあります。Randomクラスは64ビット整数値を生成でき、BigIntegerクラスを使用して任意に大きな整数も生成できます。
より大きな整数
64ビット整数を生成するには、Random::nextInt64()関数を使用します:
auto randomInt = juce::Random::getSystemRandom().nextInt64();
これは−9,223,372,036,854,775,808 .. 9,223,372,036,854,775,807の範囲の整数を生成します。64ビット整数のより小さな範囲を指定するには、自分で算術演算を行う必要があります。
任意に大きな整数を生成するには、Random::nextLargeNumber()関数を使用します。これはBigIntegerオブジェクトを返し、最大値を示す単一のBigIntegerオブジェクトを引数として取ります(ここでも最大値は_排他的_です):
void runExample()
{
logMessage ("------------------------- START --------------------------");
juce::BigInteger maximumValue;
maximumValue.setBit (256);
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomInt = juce::Random::getSystemRandom().nextLargeNumber (maximumValue);
logMessage (randomInt.toString (10));
}
logMessage ("----------------------- FINISHED -------------------------");
}
ここでは、maximumValue BigIntegerオブジェクトのビット256を設定することで、2^256の非常に大きな最大値を作成しています。出力は非常に大きなランダムに生成された整数です。例えば:
...
104920467355765962354471349450287456411052143302125448224736731840703932891139
113537286625531390989138082815985050172086775818737779507269901485377454431686
57847088262227027688174446009482649397747696846679750345153185249067937632876
54822036781617285561665007930420018266697875685845320423632890189355412858007
69785221527278648790869522951189615281519473001003509768672723497611119666776
53474255551114041705196319572561227500136240211575200675493708156631961926592
99704173402721617789464355135995656339243151513499563088758939994549638940776
89393625021259981953975863158742834192382645809510667002452217673394247775970
16555501734946882319812302845214545517919239951054372751749796998179564377182
----------------------- FINISHED -------------------------
浮動小数点値
Randomクラスは浮動小数点値も生成できます:
void runExample()
{
logMessage ("------------------------- START --------------------------");
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomValue = juce::Random::getSystemRandom().nextFloat();
logMessage (juce::String (randomValue));
}
logMessage ("----------------------- FINISHED -------------------------");
}
また、double値も生成できます:
auto randomValue = juce::Random::getSystemRandom().nextDouble();
これらの両方の場合、返される値は0.0 .. 1.0の範囲です。異なる範囲を生成するには、自分で算術演算を適用する必要があります(例についてはチュートリアル:ホワイトノイズジェネレータを構築するを参照)。これにはjmap()関数を使用できます。
値の可視化
長い数字のリストを見るよりも、生成されたランダム値を可視化すると便利な場合があります。これは、一様分布以外の分布を持つランダム値のセットが必要な場合に特に便利です。これにより、いくつかの興味深いパターンも作成できます。
ランダムな矩形位置
MainContentComponentを以下のシンプルなコードに変更してください:
class MainContentComponent : public juce::Component
{
public:
MainContentComponent()
{
setSize (600, 600);
}
~MainContentComponent() {}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
これにより、以下のような画像が生成されるはずです:

ウィンドウのサイズを変更してみてください。ウィンドウがリサイズされてpaint()関数が呼び出されるたびに、新しい矩形のセットが生成されることに気づくでしょう。
演習:矩形の数とサイズを変更してみてください。例えば、矩形のサイズをウィンドウのサイズに比例させることもできます。矩形の代わりに円を描いてみてください。
ランダムな矩形サイズ
矩形のサイズをランダム化するには、次のようにします:
void paint (Graphics& g) override
{
g.fillAll (juce::Colours::black);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
rect.setSize (random.nextInt (getWidth() / 4), random.nextInt (getHeight() / 4));
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}
毎回正方形を生成するようにするには、次のようにします:
for (auto i = 0; i < 100; ++i)
{
auto size = random.nextInt (jmin (getWidth(), getHeight()) / 4);
rect.setSize (size, size);
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
ランダムな色
Coloursもランダム化できますが、ランダム化を使用してうまく合う色を作成するには少しトリッキーです。例えば、これは各矩形に対して新しいランダムな色を生成します。しかし、この色は_どんな_色でも可能です:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
juce::Colour colour (random.nextInt (256),
random.nextInt (256),
random.nextInt (256));
g.setColour (colour);
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}
これは以下のようになるはずです:

シンプルな方法で色を制限するには、要素(赤、緑、または青)の1つまたは2つのみをランダム化し、値の範囲を制限することができます:
juce::Colour colour (random.nextInt (juce::Range<int> (100, 256)),
random.nextInt (juce::Range<int> (50, 200)),
200);
g.setColour (colour);
これにより、ピンクと紫の色合いの色が生成されます:

より具体的にするには、色相、彩度、明度を使用して色を生成できます。例えば、オレンジは約30°(または0 .. 1範囲で0.083)の色相にあります。0.05 .. 0.15の範囲でランダムな色相を生成すると、これらはすべて異なるオレンジの色合いになるはずです。
for (auto i = 0; i < 100; ++i)
{
auto hue = juce::jmap (random.nextFloat(), 0.05f, 0.15f);
g.setColour (juce::Colour::fromHSV (hue, 0.9f, 0.9f, 1.0f));
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}

演習:うまく合う他のランダムな色のコレクションを生成してみてください。
配列からランダムに選択
配列を使用して特定の色のセットからランダムに選択できます:
class MainContentComponent : public Component
{
public:
MainContentComponent()
{
colours.add (juce::Colours::white);
colours.add (juce::Colours::orange);
colours.add (juce::Colours::lightgreen);
setSize (400, 400);
}
~MainContentComponent() {}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
auto& random = Random::getSystemRandom();
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
for (auto i = 0; i < 1000; ++i)
{
g.setColour (colours[random.nextInt (colours.size())]);
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}
private:
juce::Array<Colour> colours;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
ここでは、アイテムを配列に格納しています。ランダムにアイテムを選択するには、配列のサイズに基づいてランダムな値を生成します。次に、この値を配列へのインデックスとして使用します。
その他の分布
このチュートリアルでこれまでに生成されたすべての乱数は、最小値と最大値の間で均等に分布していました。これは統計に関するチュートリアルではありませんが、特定のランダムに生成された値が他の値よりも発生しやすくするいくつかの簡単な方法を知っておくと便利です。これはゲーム、コンピュータ生成アート、ジェネレーティブサウンドや音楽に応用できます。
以下のコードでは、ウィンドウ全体に均等に分布した1,000個の円を生成します:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
for (auto i = 0; i < 1000; ++i)
{
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}

画像から、円がほぼ均等に分布していることがわかります。
線形分布は、範囲の一端の値が他の値よりも可能性が高い分布です。範囲全体の値は、もう一方の端に向かって可能性が低くなります。これら2点間の確率は線形に変化します。これを実装する簡単な方法は、別のランダム値の結果に基づいて1つのランダム値を生成することです。以下の例では、円のxとy座標の両方に線形分布値を生成します。
for (auto i = 0; i < 1000; ++i)
{
auto x = random.nextInt (random.nextInt (Range<int> (1, getWidth())));
auto y = random.nextInt (random.nextInt (Range<int> (1, getHeight())));
rect.setCentre (x, y);
g.drawEllipse (rect, 1.0f);
}
ここでは、1つのランダム値を生成し、その結果をRandom::nextInt()関数に渡して最終値を生成しています。
Random::nextInt()関数に渡す最大値としてゼロを使用することはできないため、最初の乱数が最低1であることを確認する必要があります。
結果は以下のようになるはずです:

ゼロに向かう値がより可能性が高いため、円は左上隅を中心に集まっているように見えます。円を中央に集中させるには、コードを次のように変更できます:
auto centreX = getWidth() / 2;
auto centreY = getHeight() / 2;
for (auto i = 0; i < 1000; ++i)
{
auto x0 = random.nextInt (juce::Range<int> (1, getWidth() / 2));
auto x = random.nextInt (juce::Range<int> (centreX - x0, centreX + x0));
auto y0 = random.nextInt (juce::Range<int> (1, getHeight() / 2));
auto y = random.nextInt (juce::Range<int> (centreY - y0, centreY + y0));
rect.setCentre (x, y);
g.drawEllipse (rect, 1.0f);
}
重み付き分布
場合によっては、特定の結果の確率を具体的に重み付けしたい場合があります。配列から3つの色をランダムに選択した以前の例を取り上げましょう。白が最も可能性が高く、オレンジがそれほど可能性が高くなく、緑がオレンジよりもさらに可能性が低いとしましょう。以下の表に示すように確率を定義できます:
| 色 | 確率 |
|---|---|
| 白 | 70% |
| オレンジ | 25% |
| 緑 | 5% |
重みのインデックスが色のインデックスに対応する追加の_重み_配列をコードに追加できます:
class MainContentComponent : public juce::Component
{
public:
MainContentComponent()
{
colours.add (juce::Colours::white);
weights.add (0.7f);
colours.add (juce::Colours::orange);
weights.add (0.25f);
colours.add (juce::Colours::lightgreen);
weights.add (0.05f);
setSize (400, 400);
}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
juce::Random random;
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
auto sumOfWeights = sumFloatArray (weights); // [1]
for (auto i = 0; i < 1000; ++i)
{
g.setColour (colourAtQuantile (random.nextFloat() * sumOfWeights)); // [2]
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}
private:
static float sumFloatArray (const juce::Array<float>& values)
{
auto sum = 0.0f;
for (auto value : values)
sum += value;
return sum;
}
static int indexOfQuantile (const juce::Array<float>& weights, float quantile)
{
auto runningTotal = 0.0f; // [4]
for (auto weight : weights)
{
runningTotal += weight;
if (quantile < runningTotal) // [5]
return weights.indexOf (weight);
}
return -1;
}
juce::Colour colourAtQuantile (float quantile) const // [3]
{
auto index = indexOfQuantile (weights, quantile);
return index < 0 ? juce::Colours::transparentBlack : colours[index];
}
//==============================================================================
juce::Array<juce::Colour> colours;
juce::Array<float> weights;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
これは以下のように機能します:
- [1]:乱数を生成するには、まず重みの合計を取ります。この場合、重みの合計が1.0であることがわかっていますが、この技術を使用すると、重みは1.0に合計する_必要_はありません。
- [2]:次に、
0 .. sumの範囲で乱数を生成します。 - [3]:
colourAtQuantile()関数はこの値を使用して色を見つけます。 - [4]:これは、重みの配列を反復処理しながら重みの累計を保持することで達成されます。
- [5]:累計がランダムに生成された値を超えると、色が見つかります。
この最後の例のコードは、デモプロジェクトのRandomTutorial_02.hファイルにあります。
結果は以下のスクリーンショットに示されています:

まとめ
このチュートリアルでは、Randomクラスと乱数生成全般について紹介しました。このチュートリアルを読んだ後、以下のことができるはずです:
- ランダムな整数と浮動小数点値を生成する。
- 生成される乱数の範囲を制限する。
- ランダムな値を使用して描画タスクを実行する。
- ランダムに生成される色の特定の範囲を生成する。
- 配列からランダムに値を選択し、これらの選択の確率を重み付けすることを含む。