6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DelphiAdvent Calendar 2024

Day 13

[Delphi] 乱数マネージャの作成

Last updated at Posted at 2024-12-12

はじめに

9日11日12日と3回に渡って乱数の紹介をしてきました。
ここからが本番です。

乱数マネージャの作成

過去3回の開設で、乱数には得手不得手がある事がわかりました。

アルゴリズム メリット デメリット
線形合同法 実装が簡単 周期が短い
CNG Random 暗号用途に使用可能 Windows API なので他ではつかえない
XorShift 実装が簡単・高速 とくになし
Mersenne Twister 周期がとてつもなく長い 計算資源をそれなりに使う

得手不得手に応じて、これらを簡単に切り替えられたら良いですよね。

TBitmapCodecManager のように、上記4つの乱数を切り替えられる、TRandomManager を作りましょう。

TRandomManager

TRandomManager の機能は

  • 乱数アルゴリズムの登録
  • 乱数アルゴリズムの呼び出し

とします。
実際の乱数は Random を呼んで実行するようにします。
(TBitmapCodecManager がコーデックを管理して、実際の使用は TBitmap を使う様な関係です)

登録機構を作る

乱数アルゴリズムは class として実装します。
(前回までは record で実装しました)

そのため、乱数アルゴリズムクラスを生成する機構が必要です。
そこで、乱数アルゴリズムクラスを生成する IRandomAlgoBuilder インターフェースを定義します。

  IRandomAlgoBuilder = interface
  ['{4461DD5C-6BCF-4F3E-A93D-24ED90E157BB}']
    function CreateAlgo: IRandomAlgo;
  end;

この IRandomAlgoBuilder を TRandomManager に登録できるようにします。
必要になったら IRandomAlgoBuilder を呼び出して利用する乱数アルゴリズム本体を生成する仕組みです。

Builder を登録するメソッド
class procedure RegisterAlgo(
  const AName: String;               // 乱数アルゴリズムの名前
  const ABuilder: IRandomAlgoBuilder // 乱数アルゴリズムを生成するビルダー
);

TXorShiftAlgoBuilder を登録する場合は↓のようになります。

使用例
initialization
  TRandomManager.RegisterAlgo(TXorShiftAlgo.NAME, TXorShiftAlgoBuilder.Create);

IRandomAlgo の定義

各アルゴリズムが実装しなければいけないインターフェース IRandomAlgo を作成します。
IRandomAlgoBuilder.CreateAlgo はこれを実装したクラスを生成して返します。

  IRandomAlgo = interface
  ['{6508CC6E-67CD-4F63-B4FF-5F6461F2FE76}']
    procedure Initialize;
    procedure SetSeed(const ASeed: UInt64);
    function Execute: UInt32;
    function GetName: String;
    property Name: String read GetName;
  end;

全ての乱数クラスが IRandomAlgo を実装します。
そして、そのたびに class(TInterfacedObject, IRandomAlgo) と書くのが面倒なので、基底クラスを作りました。
IRandomAlgo のメソッドは全て abstract で定義されています。

  TRandomAlgo = class(TInterfacedObject, IRandomAlgo)
  protected
    procedure Initialize; virtual; abstract;
    procedure SetSeed(const ASeed: UInt64); virtual; abstract;
    function Execute: UInt32; virtual; abstract;
    function GetName: String; virtual; abstract;
  public
    property Name: String read GetName;
  end;

これも TXorShiftAlgo を例に説明します。

  TXorShiftAlgo = class(TRandomAlgo)
  private const
    NAME = 'XorShift';
  private var
    FA: UInt32;
  private
    procedure Initialize; override;
    procedure SetSeed(const ASeed: UInt64); override;
    function Execute: UInt32; override;
    function GetName: String; override;
  end;

TRandomAlgo クラスを継承してメソッドを実装しています。

function TXorShiftAlgo.Execute: UInt32;
begin
  FA := FA xor (FA shl 13);
  FA := FA xor (FA shr 17);
  FA := FA xor (FA shl 5);
  Result := FA;
end;

function TXorShiftAlgo.GetName: String;
begin
  Result := NAME;
end;

procedure TXorShiftAlgo.Initialize;
begin
  FA := Round(Frac(Now) * 24 * 60 * 60 * 1000) mod 1000;;
end;

procedure TXorShiftAlgo.SetSeed(const ASeed: UInt64);
begin
  DefaultRandomize(ASeed);
  FA := RandSeed;
end;

利用方法

最後に利用方法です。
PK.Math.Random.RandomManager を uses して各アルゴリズムを表す文字列でアルゴリズムを指定し、Random や Randomize を呼ぶだけです。

uses
  PK.Math.Random.RandomManager;

procedure Sample;
begin
  TRandomManager.Current.SetAlgo('XorShift');

  Randomize;
  Writeln(Random(10));
end;

今まで紹介してきたアルゴリズムが使えます。

アルゴリズム 文字列 Unit
線形合同法 LCG PK.Math.Random.LCG.pas
CNG Random CNG PK.Math.Random.CNG.pas
XorShift XorShift PK.Math.Random.XorShift.pas
MersenneTwister MT PK.Math.Random.MersenneTwister.pas

ソース

下記からダウンロードできます。
https://github.com/freeonterminate/RandomManager

まとめ

内部に全アルゴリズムを内包しているので、ちょっと効率が悪いですね…
何か良い方法はないかなと思った物の時間切れ…

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?