delphi – 总数相等的随机数
我有4个号码
a,b,c,d : integers 我需要在2-7之间分配一个随机数,但所有四个数字的总数必须为22 我怎样才能做到这一点? 解决方法
首先,我要说清楚,如上所述,问题不能唯一地定义问题.您要求随机抽样,但不指定所需的样本分布.
当你实际上意味着均匀分布时,通常滥用数学术语来说随机.所以我会假设这就是你的意思.具体而言,您希望所有可能不同的4个数字组具有相同的选择概率.实现这一目标的最简单,最有效的方法如下: >列举所有这些可能的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的代码.它错误地使用了
关键是该范围被定义为闭合开放区间.返回的值在[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图变为: 所有这一切的底线是,如果你关心分配,很难做到抽样. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |