2006. július 13., csütörtök

Random Number Engine with UNIQUE property

Problem/Question/Abstract:

This class can be used to generate random numbers from MinNumber to MaxNumber returning a number only once if Unique is set. A check is also available to see if the numbers has been used. A dynamic TBit array is used to ensure numbers are returned only once and to see if ALL the numbers have been used.

Useful particularly for card games eg. random numbers from 1 to 52, and once drawn do not draw same card again until pack is empty.

properties

MinNumber Random Number from.
MaxNumber Random Number to.
NumbersTotal Total available numbers.
NumbersUsed  Unique numbers used of total.
NumbersFree Unique numbers free of total.
Unique If true then a number will not be repeated. If false then numbers may be repeated.

methods

IsUsed(Index) Returns true if UNIQUE used and number has been used.

Reset Resets the NumbersUsed and NumbersFree (Used by UNIQUE)

GetRandom Returns a random number from MinNumber  to MaxNumber
(will not repeat a number if UNIQUE is set)

// =========================
// Simple Example
// =========================

procedure TForm1.Button2Click(Sender: TObject);
var
R: TRandomEngine;
begin
R := TRandomEngine.Create;
R.MinNumber := 1;
R.MaxNumber := 52;
R.Unique := true;

repeat
Memo1.Lines.Add(inttostr(R.GetRandom) + '   ' +
inttostr(R.NumbersTotal) + '  ' +
inttostr(R.NumbersUsed) + '  ' +
inttostr(R.NumbersFree));
until R.NumbersFree = 0;

R.Free;
end;

Answer:

unit REngine;
interface

uses Windows, Classes;

type
TRandomEngine = class(TObject)
private
FSelected: TBits;
FArrSize, FNumbersUsed: longint;
FMinNumber, FMaxNumber: longint;
FUnique: boolean;
procedure SizeSelArray;
procedure SetFMinNumber(NewValue: longint);
procedure SetFMaxNumber(NewValue: longint);
function GetFNumbersFree: longint;
public
constructor Create;
destructor Destroy; override;
procedure Reset;
function GetRandom: longint;
function IsUsed(Index: longint): boolean;
property MinNumber: longint read FMinNumber write SetFMinNumber;
property MaxNumber: longint read FMinNumber write SetFMaxNumber;
property Unique: boolean read FUnique write FUnique;
property NumbersUsed: longint read FNumbersUsed;
property NumbersTotal: longint read FArrSize;
property NumbersFree: longint read GetFNumbersFree;
end;

// -------------------------------------------------------------
implementation

// ===================================
// Create and Free the Object
// ===================================

constructor TRandomEngine.Create;
begin
FSelected := TBits.Create;
FNumbersUsed := 0;
FMinNumber := 0;
FMaxNumber := 0;
FArrSize := 0;
FUnique := false;
Randomize;
end;

destructor TRandomEngine.Destroy;
begin
inherited Destroy;
FSelected.Free;
end;

// ===========================
// Property Get/Set methods
// ===========================

procedure TRandomEngine.SetFMinNumber(NewValue: longint);
begin
if (NewValue <> FMinNumber) then
begin
FMinNumber := NewValue;
if FMinNumber > FMaxNumber then
FMaxNumber := FMinNumber;
SizeSelArray;
end;
end;

procedure TRandomEngine.SetFMaxNumber(NewValue: longint);
begin
if (NewValue <> FMaxNumber) then
begin
FMaxNumber := NewValue;
if FMaxNumber < FMinNumber then
FMinNumber := FMaxNumber;
SizeSelArray;
end;
end;

function TRandomEngine.GetFNumbersFree: longint;
begin
Result := FArrSize - FNumbersUsed;
end;

// =======================================
// Resize the boolean array (FSelected)
// =======================================

procedure TRandomEngine.SizeSelArray;
var
i: longint;
begin
FArrSize := FMaxNumber - FMinNumber + 1;

if FArrSize > 0 then
begin
FSelected.Size := FArrSize;
for i := 0 to FArrSize - 1 do
FSelected[i] := false;
end;

FNumbersUsed := 0;
end;

// =======================================
// Reset avail,used and free numbers.
// Reset FSelected array to false for
// IsUsed()
// =======================================

procedure TRandomEngine.Reset;
begin
SizeSelArray;
end;

// ===================================================
// Return true/false if numbers has been used if
// Unique is set
// ===================================================

function TRandomEngine.IsUsed(Index: longint): boolean;
var
Retvar: boolean;
begin
if (Index < FMinNumber) or (Index > FMaxNumber) then
Retvar := false
else
RetVar := FSelected[Index - FMinNumber];

Result := RetVar;
end;

// ===================================================
// Return a random number based on Min - Max
// If Unique then generate based on FSelected
// array (ie. make sure number has not been used
// ===================================================

function TRandomEngine.GetRandom: longint;
var
V: longint;
NumSelected: boolean;
begin
if FUnique and (FNumbersUsed = FArrSize) then
V := 0
else
begin
repeat
V := Random(FMaxNumber - FMinNumber + 1) + FMinNumber;
if not FUnique then
NumSelected := true
else
begin
if FSelected[V - FMinNumber] then
NumSelected := false
else
begin
NumSelected := true;
FSelected[V - FMinNumber] := true;
inc(FNumbersUsed);
end;
end;
until NumSelected;
end;

Result := V;
end;

end.


Nincsenek megjegyzések:

Megjegyzés küldése