我是 C# 新手。
我想要做什么
我正在尝试在这里创建一个机会游戏系统。
基本上是这样的:
我的问题:我该怎么做才能完成我想做的事情?
Your example code has a hard bug: you've written 150/208
and 190/209
. This is an integer division, and both result in zero. You should have written: 150.0/208
and 190.0/209
to instruct the compiler to divide them as double's not integers.
Edit:
Assuming the system's RNG is flat and that your table is as follows:
[item] [amount]
0 3 000 000
25 1 500 000
50 2 000 000
75 300 000
100 10 000
150 10 000 (no typo)
sum = 6820000
Then your randomizer can look like:
int randomItemNumber = Random.Next(6820000); // 0..6819999
if(randomItemNumber < 3000000)
Console.WriteLine("Aah, you've won the Item type #0\n");
else if(randomItemNumber < 3000000+1500000)
Console.WriteLine("Aah, you've won the Item type #1\n");
else if(randomItemNumber < 3000000+1500000+2000000)
Console.WriteLine("Aah, you've won the Item type #2\n");
else if(randomItemNumber < 3000000+1500000+2000000+300000)
Console.WriteLine("Aah, you've won the Item type #3\n");
else if(randomItemNumber < 3000000+1500000+2000000+300000+10000)
Console.WriteLine("Aah, you've won the Item type #4\n");
else if(randomItemNumber < 3000000+1500000+2000000+300000+10000+10000)
Console.WriteLine("Aah, you've won the Item type #5\n");
else
Console.WriteLine("Oops, somehow you won nothing, the code is broken!\n");
The idea is that you put all the items in a looong line, one after another, but you keep them in their groups. So, at start there's three milion of the first kind, then a milion-and-half of the second type and so on. There are in total 6820000 items in the line. Now you randomly pick a number from 1 to 6820000 (or from 0 to 6819999) and use it as the NUMBER of an element in the LINE.
As the items are present in the line with their correct statistical distribution, then if the randomization 1-6820000 was FLAT, then the resulting 'lottery' will have distribution exactly as you you wanted.
The only trick left to explain, is how to guess what item was picked. This is why we kept the items in groups. The first part of 3000000 items is the first type, so if the number was less than 3000000 then we hit the first type. If more than that, but lower than the next 1500000 (lower than 4500000) then the second type is hit.. and so on.
正如其他人所说,您的代码有一个整数除法错误。
在任何情况下,您都需要查看:逆变换采样。
基本上,它允许您获取一个统一的随机数(大多数 PRNG 给您的)并将其转换为来自任何分布的随机样本。为此,您需要使用目标分布的 CDF。
参考资料和有用的页面:
[引用历史记录]
编辑:我实际上是指分类分布,而不是多项分布。这两个分布经常混为一谈(尤其是在我的领域),但区别很重要。只有当多项分布参数化为 n = 1(即一次试验)时,这两个分布才等效。
一个除数必须是 double 以防止除零。要计算您需要将它们累积到 100%(或 1)的概率:
// Element - Probability - Cumulative Probability
// Item100 10000 / 6820000 0.001466275659824
// Item75 300000 / 6820000 0.0439882697947214 + 0.001466275659824
// Item50 2000000 / 6820000 0.2932551319648094 + 0.0454545454545454
// Item25 1500000 / 6820000 0.219941348973607 + 0.3387096774193548
const double Item100 = 0.001466275659824;
const double Item75 = 0.0454545454545454;
const double Item50 = 0.3387096774193548;
const double Item25 = 0.5586510263929618;
int getRandomItem(Random rnd)
{
double value = rnd.NextDouble();
if (value <= Item100)
{
// use one of both possible items (100 or 150)
int which = rnd.Next(0, 2);
return which == 0 ? 100 : 150;
}
else if (value <= Item75)
return 75;
else if (value <= Item50)
return 50;
else if (value <= Item25)
return 25;
else
return 0;
}
你将如何使用它:
var rnd = new Random();
var items = new List<int>();
for (int i = 0; i < 100; i++)
items.Add(getRandomItem(rnd));
Console.Write(string.Join(Environment.NewLine, items));
请注意,我重用了随机实例。如果我在循环中创建它,“随机值将始终相同,因为它将在同一时间播种。
我在我的应用程序中做了类似的事情,并将其转换为下面的问题:在伪代码中:
类 Items 如下所示(删除了一些不重要的行并添加了注释 //
public class Items : List<Item>
{
public Items()
{
Add(new Item( 0, 3000000));
Add(new Item(25, 1500000));
Add(new Item(50, 2000000));
// etc
}
/// <summary>
/// Returns a random item based on value.
/// </summary>
/// <returns></returns>
public Item GetRandomItem()
{
var sum = this.Sum(item => item.Value);
var randomValue = new Random().Next(sum);
// Iterate through itemsuntil found.
var found = false;
var itemIndex = 0;
var visitedValue = 0;
while (!found)
{
var item = this[itemIndex];
if ((visitedValue + item.Value ) > randomValue)
{
found = true;
}
else
{
itemIndex++;
visitedValue += item.value;
}
}
return this[itemIndex];
}
Item 类只不过是 Name 和 Value 的占位符。
它看起来很长,但它有一些好处:
这样的事情应该做你。也许不是世界上最好的例子,但它应该足够了:
class Item
{
public string Name { get ; private set ; }
public int Amount { get ; private set ; }
public Item( string name , int amount )
{
if ( string.IsNullOrWhiteSpace(name) ) throw new ArgumentException("name") ;
if ( amount < 0 ) throw new ArgumentException("amount") ;
this.Name = name ;
this.Amount = amount ;
return ;
}
}
static void Main( string[] args )
{
Random rng = new Random() ;
Item[] items = { new Item( "item--0" , 3000000 ) ,
new Item( "item-25" , 1500000 ) ,
new Item( "item-50" , 2000000 ) ,
new Item( "item-75" , 300000 ) ,
new Item( "item-100" , 10000 ) ,
new Item( "item-150" , 10000 ) ,
} ;
int total = items.Sum( x => x.Amount ) ;
for ( int i = 0 ; i < 100 ; ++i )
{
int r = rng.Next(0, total ) ; // get a random integer x such that 0 <= x < total
int n = 0 ;
Item selected = null ;
int lo = 0 ;
int hi = 0 ;
for ( int j = 0 ; j < items.Length ; ++j )
{
lo = n ;
hi = n + items[j].Amount ;
n = hi ;
if ( r < n )
{
selected = items[j] ;
break ;
}
}
Console.WriteLine( "iteration {0}. r is {1} <= {2} < {3}. Selected item is {4}" ,
i ,
lo ,
r ,
hi ,
selected.Name
) ;
}
return;
}