加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

delphi – 总数相等的随机数

发布时间:2020-12-15 04:14:12 所属栏目:大数据 来源:网络整理
导读:我有4个号码 a,b,c,d : integers 我需要在2-7之间分配一个随机数,但所有四个数字的总数必须为22 我怎样才能做到这一点? 解决方法 首先,我要说清楚,如上所述,问题不能唯一地定义问题.您要求随机抽样,但不指定所需的样本分布. 当你实际上意味着均匀分布时,通
我有4个号码
a,b,c,d : integers

我需要在2-7之间分配一个随机数,但所有四个数字的总数必须为22

我怎样才能做到这一点?

解决方法

首先,我要说清楚,如上所述,问题不能唯一地定义问题.您要求随机抽样,但不指定所需的样本分布.

当你实际上意味着均匀分布时,通常滥用数学术语来说随机.所以我会假设这就是你的意思.具体而言,您希望所有可能不同的4个数字组具有相同的选择概率.实现这一目标的最简单,最有效的方法如下:

>列举所有这些可能的4个数字组.
>算上这些数字,比如N.
>要进行采样,请从0到N-1范围内的均匀分布中选择随机数.
>返回第i组4个数字.

可能的不同集的列表很小.在我的头脑中,我猜大约有50名候选人.

生成候选人列表非常简单.只需从2到7运行三个嵌套for循环.这将为您提供前三个数字的组合.将它们相加,从22减去并检查最终数字是否在范围内.

既然你似乎想看代码,这里有一个简单的演示:

{$APPTYPE CONSOLE}

uses
  System.Math,Generics.Collections;

type
  TValue = record
    a,d: Integer;
    procedure Write;
  end;

procedure TValue.Write;
begin
  Writeln(a,' ',d);
end;

var
  Combinations: TArray<TValue>;

procedure InitialiseCombinations;
var
  a,d: Integer;
  Value: TValue;
  List: TList<TValue>;
begin
  List := TList<TValue>.Create;
  try
    for a := 2 to 7 do
      for b := 2 to 7 do
        for c := 2 to 7 do
        begin
          d := 22 - a - b - c;
          if InRange(d,2,7) then
          begin
            Value.a := a;
            Value.b := b;
            Value.c := c;
            Value.d := d;
            List.Add(Value);
          end;
        end;
    Combinations := List.ToArray;
  finally
    List.Free;
  end;
end;

function GetSample: TValue;
begin
  Result := Combinations[Random(Length(Combinations))];
end;

var
  i: Integer;

begin
  Randomize;
  InitialiseCombinations;
  for i := 1 to 25 do
    GetSample.Write;
  Readln;
end.

从检查中可以清楚地看出,该算法均匀地从可用值中进行采样.

但是其他提出的算法呢?我们可以通过重复采样并计算每个可能的样本产生的次数来执行粗略的启发式测试.这里是:

{$APPTYPE CONSOLE}

uses
  System.SysUtils,System.Math,d: Integer;
    procedure Write;
    class operator Equal(const lhs,rhs: TValue): Boolean;
  end;

procedure TValue.Write;
begin
  Writeln(a,d);
end;

class operator TValue.Equal(const lhs,rhs: TValue): Boolean;
begin
  Result := (lhs.a=rhs.a) and (lhs.b=rhs.b) and (lhs.c=rhs.c) and (lhs.d=rhs.d);
end;

var
  Combinations: TArray<TValue>;

procedure InitialiseCombinations;
var
  a,7) then
          begin
            Value.a := a;
            Value.b := b;
            Value.c := c;
            Value.d := d;
            List.Add(Value);
          end;
        end;
    Combinations := List.ToArray;
  finally
    List.Free;
  end;
end;

function GetSampleHeffernan: TValue;
begin
  Result := Combinations[Random(Length(Combinations))];
end;

function GetSampleVanDien: TValue;
const
  TOTAL = 22;
  VALUE_COUNT = 4;
  MIN_VALUE = 2;
  MAX_VALUE = 7;
var
  Values: array[0..VALUE_COUNT-1] of Integer;
  Shortage: Integer;
  Candidates: TList<Integer>;
  ValueIndex: Integer;
  CandidateIndex: Integer;
begin
  Assert(VALUE_COUNT * MAX_VALUE >= TOTAL,'Total can never be reached!');
  Assert(VALUE_COUNT * MIN_VALUE <= TOTAL,'Total is always exceeded!');
  Randomize;
  Candidates := TList<Integer>.Create;
  try
    for ValueIndex := 0 to VALUE_COUNT-1 do
    begin
      Values[ValueIndex] := MIN_VALUE;
      Candidates.Add(ValueIndex);
    end;
    Shortage := TOTAL - VALUE_COUNT * MIN_VALUE;
    while Shortage > 0 do
    begin
      CandidateIndex := Random(Candidates.Count);
      ValueIndex := Candidates[CandidateIndex];
      Values[ValueIndex] := Values[ValueIndex] + 1;
      if Values[ValueIndex] = MAX_VALUE then
        Candidates.Remove(CandidateIndex);
      Shortage := Shortage - 1;
    end;
  finally
    Candidates.Free;
  end;

  Result.a := Values[0];
  Result.b := Values[1];
  Result.c := Values[2];
  Result.d := Values[3];
end;

function GetSampleLama: TValue;
type
  TRandomValues = array[1..4] of Integer;
var
  IntSum: Integer;
  Values: TRandomValues;
begin
  // initialize a helper variable for calculating sum of the generated numbers
  IntSum := 0;
  // in the first step just generate a number in the range of 2 to 7 and store
  // it to the first integer element
  Values[1] := RandomRange(2,7);
  // and increment the sum value
  IntSum := IntSum + Values[1];
  // as the next step we need to generate number,but here we need also say in
  // which range by the following rules to ensure we ever reach 22 (consider,if
  // the 1st number was e.g. 3,then you can't generate the second number smaller
  // than 5 because then even if the next two numbers would be max,you would get
  // e.g. only 3 + 4 + 7 + 7 = 21,so just use this rule:
  // Values[1] Values[2]
  //        2      6..7
  //        3      5..7
  //        4      4..7
  //        5      3..7
  //     6..7      2..7
  Values[2] := RandomRange(Max(2,8 - Values[1]),7);
  // and increment the sum value
  IntSum := IntSum + Values[2];
  // if the third step we need to generate a value in the range of 15 to 20 since
  // the fourth number can be still in the range of 2 to 7 which means that the sum
  // after this step must be from 22-7 to 22-2 which is 15 to 20,so let's generate
  // a number which will fit into this sum
  Values[3] := RandomRange(Max(2,Min(7,15 - IntSum)),Max(2,20 - IntSum)));
  // and for the last number let's just take 22 and subtract the sum of all previous
  // numbers
  Values[4] := 22 - (IntSum + Values[3]);

  Result.a := Values[1];
  Result.b := Values[2];
  Result.c := Values[3];
  Result.d := Values[4];
end;

function IndexOf(const Value: TValue): Integer;
begin
  for Result := 0 to high(Combinations) do
    if Combinations[Result] = Value then
      exit;
  raise EAssertionFailed.Create('Invalid value');
end;

procedure CheckCounts(const Name: string; const GetSample: TFunc<TValue>);
const
  N = 1000000;
var
  i: Integer;
  Counts: TArray<Integer>;
  Range: Integer;
begin
  SetLength(Counts,Length(Combinations));
  for i := 1 to N do
    inc(Counts[IndexOf(GetSample)]);
  Range := MaxIntValue(Counts) - MinIntValue(Counts);
  Writeln(Name);
  Writeln(StringOfChar('-',Length(Name)));
  Writeln(Format('Range = %d,N = %d',[Range,N]));
  Writeln;
end;

begin
  Randomize;
  InitialiseCombinations;
  CheckCounts('Heffernan',GetSampleHeffernan);
  //CheckCounts('Van Dien',GetSampleVanDien);
  CheckCounts('Lama',GetSampleLama);
  Readln;
end.

一次特定运行的输出是:

Heffernan
---------
Range = 620,N = 1000000

Lama
----
Range = 200192,N = 1000000

Van Dien变体目前被注释掉,因为它产生无效值.

好的,我调试并修复了Van Dien变体.测试和结果现在看起来像这样:

{$APPTYPE CONSOLE}

uses
  System.SysUtils,'Total is always exceeded!');
  Candidates := TList<Integer>.Create;
  try
    for ValueIndex := 0 to VALUE_COUNT-1 do
    begin
      Values[ValueIndex] := MIN_VALUE;
      Candidates.Add(ValueIndex);
    end;
    Shortage := TOTAL - VALUE_COUNT * MIN_VALUE;
    while Shortage > 0 do
    begin
      CandidateIndex := Random(Candidates.Count);
      ValueIndex := Candidates[CandidateIndex];
      inc(Values[ValueIndex]);
      if Values[ValueIndex] = MAX_VALUE then
        Candidates.Delete(CandidateIndex);
      dec(Shortage);
    end;
  finally
    Candidates.Free;
  end;

  Result.a := Values[0];
  Result.b := Values[1];
  Result.c := Values[2];
  Result.d := Values[3];
end;

function GetSampleLama: TValue;
type
  TRandomValues = array[1..4] of Integer;
var
  IntSum: Integer;
  Values: TRandomValues;
begin
  // initialize a helper variable for calculating sum of the generated numbers
  IntSum := 0;
  // in the first step just generate a number in the range of 2 to 7 and store
  // it to the first integer element
  Values[1] := RandomRange(2,GetSampleHeffernan);
  CheckCounts('Van Dien',GetSampleLama);
  Readln;
end.
Heffernan
---------
Range = 599,N = 1000000

Van Dien
--------
Range = 19443,N = 1000000

Lama
----
Range = 199739,N = 1000000

并且只是把它撞回家,这里是各种分布的经验概率质量函数的一些图:

好的,现在我修复了@ TLama的代码.它错误地使用了RandomRange. documentation指出:

RandomRange returns a random integer from the range that extends between AFrom and ATo (non-inclusive).

关键是该范围被定义为闭合开放区间.返回的值在[AFrom..ATo]范围内,或用不等号表示,AFrom< = Value< A要. 但@ TLama的代码是在假设间隔在两端都关闭的情况下编写的.因此,通过在每次调用RandomRange的第二个参数中加1,可以很容易地修复代码.当我们这样做时,输出看起来像这样:

Heffernan
---------
Range = 587,N = 1000000

Van Dien
--------
Range = 19425,N = 1000000

Lama
----
Range = 79320,N = 1000000

并且经验PMF图变为:

所有这一切的底线是,如果你关心分配,很难做到抽样.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读