PIDを使用したバイナリツリーを用いた衝突しない乱数の生成

'PIDを使用したバイナリツリーによる衝突しない乱数生成'

ランダムな数値(真の値または擬似値)は、セキュリティ、金銭取引、機械学習、ゲーム、統計、暗号化、シミュレーション、文学、音楽、芸術など、さまざまな目的に広く利用されています。

非衝突ランダム数を生成するためのさまざまな手法とアルゴリズムが存在します。その中でも広く使われている手法には、Fischer/Knuth Yates アルゴリズムがあります。この記事では、API を使用せずにプロセスID(PID)を持つバイナリツリーを使って、非衝突の擬似乱数を生成する方法を紹介します。

ランダム数

ランダム数は、ランダムなチャンスよりも優れた方法で合理的に予測することができない任意の数値のシーケンスです。つまり、特定の結果のシーケンスは、後知恵で検出可能なパターンを含むが、予測には予測不可能です。

Java の SecureRandom が導入される前の Oracle JDK 7 の java.util.Random の実装は、線形合同法ジェネレーター(Linear Congruential Generator)メソッドを使用して、予測しやすく壊れやすいランダムな値を生成します。一方、SecureRandom は、OS からランダムなデータを取得し、シードとして使用し、非決定的な出力を生成する、暗号強度の高いランダム数を提供します。(Linux/Solaris の場合は、/dev/random および /dev/urandom にほとんどの場合保存されます。)

この記事では、物理環境の属性の現在の値によって常に変化し、実際にモデル化することがほぼ不可能な方法でランダム数を生成する真のランダム数ジェネレーターには焦点を当てていません。代わりに、Fischer/Knuth Yates アルゴリズムに似たバイナリツリーとプロセスID(PID)を使用して非衝突の擬似乱数を生成する別の方法を提供します。

非衝突で一意のランダム数の生成(HashTableを使用)

以下の例に示すように、HashTable の実装は、非衝突のランダム数を生成する一般的な手法です。

HashTable を使用して非衝突で一意のランダム数を生成する方法:

上記の方法(他のプログラミング言語の実装と同様)は機能しますが、以下のような多くの欠点があります:

  1. メモリ使用量:ハッシュされた値を格納し、内部データ構造を維持するために追加のメモリが必要です。データセットが大きくなるにつれて、HashTable のメモリ使用量も増加します。
  2. ハッシュの衝突:HashTable は、定義されたランダムなサイズに基づいて衝突の可能性があります。異なる入力値が同じハッシュ値を生成する場合があります。また、セパレートチェイニングやオープンアドレッシングなどの衝突処理メカニズムが存在しますが、それらは複雑さを導入し、パフォーマンスに影響を与えます。
  3. パフォーマンスのオーバーヘッド:HashTable に要素を挿入したり、検索したりする場合、ハッシュ関数の計算や衝突解決構造の走査が必要となります。これらの操作は、特に要素数が多い場合に、追加の計算オーバーヘッドを導入します。データセットのサイズが増加するにつれて、HashTable のパフォーマンスは低下します。
  4. 連続性の欠如:HashTable は、連続的または順序付けされたランダム数を生成するための組み込みのメカニズムを提供しません。生成されたランダム数は、見かけ上ランダムな順序で表示され、連続性が望ましい特定のアプリケーションには適していない場合があります。
  5. ハッシュ関数の選択:HashTable の効果は、使用されるハッシュ関数の品質に依存します。値を均等に分布させ、衝突を最小限に抑える適切なハッシュ関数を選択することは難しいです。不適切なハッシュ関数は、衝突率が高くなり、生成された数値のランダム性と一意性に影響を与える可能性があります。

非衝突で一意のランダム数の生成(バイナリツリーとPIDを使用)

プロセスIDまたはPIDは、ほとんどのオペレーティングシステムカーネル(Unix、macOS、Windowsなど)で使用されるプロセス識別子です。たとえば、LinuxのPID値は、オペレーティングシステムカーネルによって/procディレクトリに格納および管理されます。各実行中のプロセスは、識別のためにカーネルによって一意のPIDが割り当てられるため、この実装にはPIDの前提条件があります。

バイナリツリーPIDアプローチ

  • ステップ0:数値のデータセットで開始します。
    • 例: n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
  • ステップ1:ルートノードを持つバイナリツリー構造を作成します。
  • ステップ2:ツリーをトラバースするためのランダムサイクル(c)を開始します。
    • デフォルトでは、c = 0 で、各ブランチのルート、左、右ノードが完全に訪問されるまでサイクルは完了します。
  • ステップ3:データセットの中央インデックス(n/2)によって決定されるルートノードから開始します。
    • c = 0 の場合、root_node = n / 2
  • ステップ4:現在のサイクルに基づいて、左ノードまたは右ノードをトラバースします。
    • 左ノード:c
    • 右ノード:(n – 1) – c
  • ステップ5:ルート、左、右ノードが訪問された場合にサイクルを完了するか、最後のサイクルであるかどうかをチェックします。つまり、(c * 3) == (n – 1) かどうかを確認します。
  • ステップ6:完了チェック

すべてのノードが訪問された場合(c * 3 == n – 1)、次のサイクル(c)を1増やし、与えられたインデックス(n)を1減らし、ステップ3から6を繰り返すか、すべてのノードが完全に訪問されたかどうかを確認します(つまり、n / 2 + 1 == Root(n-1))。

例の手順

例:x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1. サイクル(c)= 0で開始

最初のサイクル(c)= 0

ルートノード = n / 2(nはデータの総数)

10 / 2 = 5

X[5] = 6

左ノード = c ← 0

X[0] ← 1

右ノード = (n – c)(c = 0の場合、1とする、最初のトラバース)

(10 – 1) ← x[9] ← 10

  • 注意:サイクル(0)が完了しました(つまり、そのサイクルのすべてのノードが完全に訪問されました)
  • 衝突のないゼロのランダムな数を生成 ⇐ [6, 1, 10](ルート、左、右)

[1, 10, 6](左、右、ルート)

[10, 1, 6](右、左、ルート)

2. 最後のサイクルに到達したかどうかを確認 ⇐ (c * 3) == (n – 1) または (n / 2) + 1 == (n – 1) ?

(0 * 3) != (10 – 1)

(10 / 2) + 1 != (10 – 1)

3. 次のサイクル(c)を1増やす、c ← 1

与えられたインデックス(n)を1減らす、n ← 9

2番目のサイクル(c)= 1

ルートノード = n / 2(nはデータの総数)

9 / 2 = 4

X[4] = 5

左ノード = c ← 1

X[1] ← 2

右ノード = (n – c)

(9 – 1) ← x[8] ← 9

  • サイクル(1)が完了しました(つまり、ルート、左、右がすでに訪問された)ノードが訪問されました
  • generated_random_numbers ⇐ [6, 1, 10, 5, 2, 9](ルート、左、右、ルート、左、右)

[1, 10, 6, 2, 5, 9](左、右、ルート、左、ルート、右)

[10, 1, 6, 9, 2, 5](右、左、ルート、右、左、ルート)

4. 最後のサイクルに到達したかどうかを確認 ⇐ (c * 3) == (n – 1) または (n / 2) + 1 == (n – 1)?

(1 * 3) != (9 – 1)

(9 / 2) + 1 != (9 – 1)

5. 次のサイクル(c)を1増やす、c ← 2

与えられたインデックス(n)を1減らす、n ← 8

3番目のサイクル(c)= 2

ルートノード = 8 / 2(nはデータの総数)

9 / 2 = 4

X[4] = 5

左ノード = c ← 1

X[1] ← 2

右ノード = (n – c)

(9 – 1)    ←x[8] ←9

  • サイクル(2)が完了しました(つまり、ルート、左、右がすでに訪問されました)ノードが訪問されました
  • generated_random_numbers ⇐ [6, 1, 10, 5, 2, 9]  (ルート、左、右、ルート、左、右)

[1, 10, 6, 2, 5, 9]  (左、右、ルート、左、ルート、右)

[10, 1, 6, 9, 2, 5]  (右、左、ルート、右、左、ルート)

サイクルテーブル

上記からさまざまな予測可能な、予測不可能なユニークで非衝突のランダム数パターンが生成されます:

[1.] 6, 1, 10, 5, 2, 9, 4, 3, 8, 7

[2.] 10, 1, 6, 2, 5, 9, 3, 8, 7, 4

[3.] 7, 1, 6, 10, 2, 5, 9, 3, 4, 8

[4.] 8, 4, 3, 2, 5, 9, 7, 1, 6, 10

[5.] 7, 4, 8, 3, 2, 9, 5, 10, 1, 6

図1:B-Treeを使用したランダム数アルゴリズム(著者の図)

PIDを使用したバイナリツリーの上記の実装は、非衝突のランダム数を生成するためにいくつかの利点を提供し、以下のさまざまなユースケースで有益です。

利点

  1. 非衝突のランダム数:最も重要な利点は、非衝突のランダム数を生成することです。ツリーの構造とプロセス識別子(PID)を活用することで、重複が排除され、一意のランダム数が提供されます。
  2. 決定論的で再現可能:アルゴリズムの決定論的な性質により、同じデータセットと開始条件は常に同じランダム数のシーケンスを生成します。この再現性は、テスト、デバッグ、または結果の検証に一貫したランダム数が必要なシナリオで価値があります。
  3. 効率的なメモリ使用:PIDを使用したバイナリツリーの実装は、ハッシュテーブルなどの他のアプローチと比較してメモリ効率が良いです。
  4. 連続性と順序:アルゴリズムはツリーのトラバースに基づいて連続的にランダム数を生成します。この連続性は、ユーザーが指定した不明な定義済みの順序を維持し、シミュレーションや反復アルゴリズムなど、順序付けられたランダム数が必要なアプリケーションで有利です。

利用シナリオ

  1. ゲーム:一意のランダム数を生成することは、公正なゲームプレイ、ランダムイベント、および一意の結果を保証するためにゲームで重要です。
  2. シミュレーション:シミュレーションでは、大量のランダムな入力が必要であり、非衝突のランダム数を確保することでシミュレーション結果の正確性と信頼性を向上させることができます。
  3. 暗号化:ランダム数は暗号アルゴリズムで重要な役割を果たします。生成されたランダム数の非衝突性は、暗号のセキュリティを強化することもできます。
  4. テストとデバッグ:アルゴリズムの決定論的かつ再現可能な性質により、特定のシナリオを再現するために一貫したランダム入力が必要なテストやデバッグに価値があります。

デメリット

  1. PIDへの依存:プロセス識別子(PID)への依存は、オペレーティングシステム内でのPIDの利用可能性と一意性を前提としています。この依存性は、PIDが容易にアクセスできない環境やシナリオでは、アルゴリズムの適用範囲を制限する可能性があります。
  2. PID再利用の衝突処理:オペレーティングシステムによってPIDが再利用または再利用される場合、衝突処理のメカニズムが必要になる場合があります。
  3. 並列実行:現在の実装では、ランダム数の並列実行や並行性には対応していません。
  4. 最適なツリー構造:データセットの特性に応じて、パフォーマンスを向上させ、トラバース時間を減らすために異なるツリー構造(バランス二分木など)を探索することができます。

非衝突する真と偽のランダムな数値が必要かどうかは、目標によって異なります。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more