Rubyで乱数を生成する方法まとめ:rand・srand・Randomクラスから正規分布、SecureRandomまで
乱数は、ゲーム開発、暗号化、シミュレーションの構築など、さまざまな場面で活用される重要な要素です。しかし技術的に言えば、コンピュータは純粋な計算によって真の乱数を生成することはできません。決定論的なデバイスで真にランダムな値を作り出すことは根本的に不可能であり、私たちが扱えるのは、あたかもランダムに生成されたように見える数値の連なり、すなわち「疑似乱数」だけです。
本記事では、Rubyで乱数を生成するためのさまざまな方法を紹介します。
Kernel#randで乱数を生成する
まずは最も基本的なrandメソッドから始めましょう。randを引数なしで呼び出すと、0.0以上1.0未満の浮動小数点数が返されます。
rand()
> 0.7308136972953823
整数の乱数が必要な場合は、関数に整数を渡します。すると、0以上その整数未満の範囲からランダムな整数が返されます。以下のコードを実行するたびに、0から7の間の数値が得られます。
rand(8)
> 5
特定の範囲内の乱数を取得したい場合は、Rangeオブジェクトをrandに渡します。
次の例では、下限1から上限10まで(上限を含む)の包含的なRangeを使って乱数を生成しています。
rand(1..10)
> 6
一方、次の例では上限を含まない排他的なRangeを使用しており、下限から上限未満までの乱数が生成されます。
rand(1...10)
> 9
範囲には浮動小数点数を指定することもできます。
rand(1.5..3.0)
> 1.7494305393711571
負の範囲も問題なく利用できます。
rand(-5..-1)
> -5
ただし、単一の負の数を引数として渡すと、直感に反する結果になることがあります。
rand(-100)
> 94
rand(-0.5)
> 0.7692627344737486
これは、randに引数nを渡した場合、0以上n.to_i.abs未満の乱数が返される仕様になっているためです。上記の例では、(-100).to_i.absは100、(-0.5).to_i.absは0となるため、このような結果になります。
また、rand(0)を呼び出すことはrand()と同じ意味になり、0.0以上1.0未満の乱数が返されます。
Kernel#srandで再現可能な乱数列を生成する
次の方法に進む前に、srand関数について確認しておきましょう。
Kernel#srandは、Kernel#randのシード(種)を設定するメソッドです。これを使うことで、プログラムの実行ごとに同じ乱数列を再現できるようになります。
この意味を理解するには、まず乱数がどのように生成されているのかを知る必要があります。
シードから「乱数」はどう作られるのか
前述のとおり、コンピュータは計算だけで真の乱数を生成できません。実際に行っているのは、ランダムに見える数列の生成です。コンピュータはまずシードと呼ばれる数値を起点とし、それを何らかのアルゴリズムに通すことで、一見ランダムな出力を生み出します。
このシードは、タイムスタンプやプロセスIDなど、複数の要素を組み合わせてコンピュータが自動的に生成します。乱数を要求するたびにこれらの要素は変化するため、シードも毎回異なり、結果として異なる数列、つまり異なる乱数が得られます。しかし、もし同じシードでアルゴリズムを実行すれば、毎回まったく同じ数列が得られます。これを可能にするのがKernel#srandです。
srandは主にテストの場面で役立ちます。アプリ内のランダム性に関わるコードをテストする際、「ランダムではあるが予測可能」な値を使えば検証しやすくなります。また、バグの切り分けや再現にも便利です。
以下の例では、srandでシードを設定したうえで、randを個別に呼び出して乱数をいくつか生成し、さらに乱数列を2つ生成しています。
srand(777)
rand()
> 0.152663734901322
rand()
> 0.3023566097075212
10.times.map { rand(10) }
> [7, 1, 7, 4, 7, 9, 8, 7, 2, 0]
10.times.map { rand(10) }
> [1, 2, 4, 5, 7, 1, 7, 2, 2, 7]
同じシードでもう一度srandを実行し、先ほどと同じ呼び出しを行うと、まったく同じ乱数が返されることがわかります。
srand(777)
rand()
> 0.152663734901322
rand()
> 0.3023566097075212
10.times.map { rand(10) }
> [7, 1, 7, 4, 7, 9, 8, 7, 2, 0]
10.times.map { rand(10) }
> [1, 2, 4, 5, 7, 1, 7, 2, 2, 7]
Randomクラスで乱数を生成する
乱数の生成には、Randomクラスを使うこともできます。
クラスメソッドのRandom.randは、Kernel#randと同等の基本機能に加え、浮動小数点数に対してより適切な処理を行います。
Random.rand(1...10)
> 5
Kernel#randとは異なり、Random.randに負の数や0を渡すとArgumentErrorが発生します。
正規分布に基づく乱数を生成する
現実世界の多くの事象は、正規分布(Normal Distribution)に従う傾向があります。ある値の範囲があったとしても、すべての値が均等に出現することはほとんどありません。多くの場合、データの大部分は比較的狭い範囲に集中し、極端な値は少数派になります。
例として成人男性の身長を考えてみましょう。記録されている最短の身長は54.6cm(21.5インチ)、最長は267cm(8フィート9インチ)です。人口の中の男性の身長をシミュレートするデータを生成したい場合、この範囲をそのままrandに渡すのは得策ではありません。8フィート9インチの男性が出る確率と、6フィートの男性が出る確率が同じになってしまうからです。後者のほうがはるかに一般的です。
正規分布に従うものの例は他にもあります。
- 測定誤差
- 血圧
- テストの点数
- 成人男女の体重
こうしたユースケースにより適した乱数を生成するには、rubystatsgemが利用できます。
$ gem install rubystats
require 'rubystats'
adult_male_height = Rubystats::NormalDistribution.new(178, 10)
sample = 50.times.map { adult_male_height.rng.round(1) }
> [183.2, 169.5, 189.7, 171.9, 176.0, 179.3, 189.3, 175.3, 188.3, 190.0, 185.5, 182.8, 187.2, 191.6, 185.4, 178.4, 187.1, 183.3, 189.6, 179.7, 172.7, 174.4, 153.8, 197.4, 176.0, 174.6, 181.1, 182.0, 204.7, 185.2, 175.9, 167.7, 160.6, 170.5, 169.3, 160.6, 165.6, 166.4, 182.6, 179.7, 183.1, 171.9, 185.4, 175.4, 179.7, 176.9, 160.6, 173.8, 181.9, 190.2]
上記のコードでは、男性の平均身長(178cm)と標準偏差(10cm)をNormalDistribution.newに渡し、この正規分布に従う50個の値を生成しています。数学的な背景に興味がある方は、関連記事を参照してみてください。
まとめ
本記事では、Rubyで「乱数」を生成するいくつかの方法——rand、srand、Randomクラス、そしてRubystats——を紹介しました。また、乱数がどのように作られているのかという仕組みにも簡単に触れ、決定論的なデバイスでは真の乱数を作れない理由についても解説しました。
注意すべき点として、ここで紹介したメソッドはすべてのユースケースに適しているわけではありません。ゲームの偶然性の演出やシミュレーションには十分ですが、パスワードリセットトークンの生成などセキュリティが求められる場面では、SecureRandomの使用を検討してください。SecureRandomを使えば、16進数、Base64、バイナリ、UUID形式の文字列を生成でき、単純な数値よりもはるかに推測困難な値を作り出せます。
本記事が皆さんの参考になれば幸いです。内容についてコメントや質問がある場合は、@AppSignalまでお気軽にお問い合わせください。取り上げてほしいトピックのご要望もお待ちしています。
※この記事は2018年8月1日に更新され、SecureRandomに関する注記が追加されました。
-
PHPでランダム文字列を生成する方法【初心者向けサンプルコード付き】
PHPでランダムな文字列を生成するには、いくつかの方法があります。本記事では、md5() 関数と mt_rand() 関数を組み合わせたシンプルな手法と、PHP 7以降で利用できる暗号学的に安全な random_bytes() 関数を使った方法を、サンプルコードと実行結果とともに解説します。 例1:md5() と mt_rand() を組み合わせる方法 mt_rand() で生成した乱数を md5() でハッシュ化し、substr() で先頭5文字を切り出すことで、ランダムな文字列を作成します。 <?php $res = substr(md5(mt_rand()
-
Pythonで乱数を生成する方法|randomモジュールの主要関数を解説
Pythonには乱数を生成するための関数がいくつか用意されています。これらの関数は、ゲームや抽選など、ランダムな数値が必要となるさまざまな場面で活用できます。乱数を使うには、まず random モジュールをインポートする必要があります。import random以下に、代表的な乱数生成関数を紹介します。choice() – リストから1つの要素をランダムに選択choice() 関数は、数値のコレクション(リストなど)から1つの要素をランダムに選んで返します。サンプルコードprint (リストから選ばれた乱数 : ,end=) print (random.choice([1, 4, 6, 10