【问题标题】:C# weighted random numbersC# 加权随机数
【发布时间】:2018-03-15 18:23:27
【问题描述】:

我在游戏编程方面需要帮助。

您打开一个箱子并以给定的概率找到一个物品。

物品/机会

A / 10%
乙 / 30%
C / 60%

Random random = new Random();
int x = random.Next(1, 101);

if (x < 11) // Numbers 1..10 ( A -> 10% )
{ 
     do_something1(); d
} 
else if (x < 41) // Numbers 11..40 ( B -> 30 % )
{ 
     do_something2();
}
else if (x < 101) // Numbers 41..100 ( C -> 60 % ) 
{ 
     do_something3();
}

就概率而言,这个例子真的有意义吗? 您还有其他解决方案吗?

提前谢谢你!

【问题讨论】:

  • 在我看来是一种合理的方式!
  • 在这种情况下x == 101 会发生什么?
  • 同样的解决方案在stackoverflow.com/questions/1522208/… 获得了 33 个赞,所以你应该做得很好
  • @TimothyGroote not possible 第二个参数是返回的随机数的唯一上界 msdn.microsoft.com/en-us/library/2dx6wyd4(v=vs.110).aspx
  • @fubo 是的,但是在更改分布的边界时,您不应忘记更新此示例中的每个“神奇值”。如果你这样做了,那就是虫子潜入的地方。

标签: c# unity3d random


【解决方案1】:

我意识到这有点晚了,但这里有一个没有 const 的例子,费力的 if/else 和/或 switch 语句;

public class WeightedChanceParam
{
    public Action Func { get; }
    public double Ratio { get; }

    public WeightedChanceParam(Action func, double ratio)
    {
        Func = func;
        Ratio = ratio;
    }
}

public class WeightedChanceExecutor
{
    public WeightedChanceParam[] Parameters { get; }
    private Random r;

    public double RatioSum
    {
        get { return Parameters.Sum(p => p.Ratio); }
    }

    public WeightedChanceExecutor(params WeightedChanceParam[] parameters)
    {
        Parameters = parameters;
        r = new Random();
    }

    public void Execute()
    {
        double numericValue = r.NextDouble() * RatioSum;

        foreach (var parameter in Parameters)
        {
            numericValue -= parameter.Ratio;

            if (!(numericValue <= 0))
                continue;

            parameter.Func();
            return;
        }

    }
}

用法示例:

WeightedChanceExecutor weightedChanceExecutor = new WeightedChanceExecutor(
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("A");
    }, 25), //25% chance (since 25 + 25 + 50 = 100)
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("B");
    }, 50), //50% chance
    new WeightedChanceParam(() =>
    {
        Console.Out.WriteLine("C");
    }, 25) //25% chance
);

//25% chance of writing "A", 25% chance of writing "C", 50% chance of writing "B"        
weightedChanceExecutor.Execute(); 

【讨论】:

  • 在你的情况下,适当的比率值将是 1、3 和 6(或 10、30 和 60)
  • 我实际上也考虑过回调方法,但我不记得它们在 C#/Unity xD 中被称为什么但是您的解决方案具有明显的优势,您永远不会忘记为其中一个实现方法机会。
  • 您也不会(如此容易地)意外地重构随机数生成器,忘记更新比较,或生成不必要的深度嵌套代码,忘记实现一个 switch case(或实现一个不需要的) .如果您在WeightedChanceParam 中的操作太大而无法舒适,只需实现一个方法并将其作为引用传递,您的“机会”相关代码将保持可读性。
【解决方案2】:

我同意@Timothy 的观点,我会选择一个更易于维护的解决方案,您不依赖magic numbers 来划分您的概率。另外,这是个人喜好,但我也称它为比率而不是百分比,否则“100”将成为另一个神奇的数字,并且您将自己限制在 1% 的最小概率。这样您就可以将其拆分为 1:10:200 或随心所欲:

public static readonly int RATIO_CHANCE_A = 10;
public static readonly int RATIO_CHANCE_B = 30;
//                         ...
public static readonly int RATIO_CHANCE_N = 60;

public static readonly int RATIO_TOTAL = RATIO_CHANCE_A
                                       + RATIO_CHANCE_B
                                         // ...
                                       + RATIO_CHANCE_N;

Random random = new Random();
int x = random.Next(0, RATIO_TOTAL);

if ((x -= RATIO_CHANCE_A) < 0) // Test for A
{ 
     do_something1();
} 
else if ((x -= RATIO_CHANCE_B) < 0) // Test for B
{ 
     do_something2();
}
// ... etc
else // No need for final if statement
{ 
     do_somethingN();
}

编辑:更通用的解决方案

【讨论】:

  • 您应该将 x &lt; RATIO_CHANCE_B 更改为 x &lt; RATIO_CHANCE_A + RATIO_CHANCE_B,因为使用当前解决方案的机会是 10/20/70
  • 我可以看到如果你有超过 3 条可能的路径会变得有点混乱,如果是这种情况,我可能会考虑在每个 else 块中将 x 减少前一个比率量而不是在if() 子句中添加每个后续的
  • 我什至不会将它减少前一个,而是减少当前一个:/*else*/ if ((x -= RATIO_CHANCE_CURRENT) &lt; 0) { do_sth(); }
【解决方案3】:

因此,总结这里的解决方案是一个解决任意数量机会的解决方案,无需大量 if-else 语句,而是使用 switch-case:

int[] chances = { 1, 23, 14, 49, 61 };
int totalRatio = 0;

foreach(int c in chances)
    totalRatio += c;

Random random = new Random();
int x = random.Next(0, totalRatio);

int iteration = 0; // so you know what to do next
foreach(int c in chances)
{
    iteration++;
    if((x -= c) < 0)
        break;
}

switch(iteration)
{
case 1:
case 2:
//...
default:
}

【讨论】:

  • 用 switch 语句替换 if/else 有点像给猪涂口红;)
  • 在这种情况下,我会说识别特定操作更容易,因为您可以轻松地用枚举替换整数,并且 IMO 切换案例比 if-else 更容易阅读;)
【解决方案4】:

当我结合你所有的答案时,这也应该在这里工作,对吧?

double number;
Random x = new Random();
number = x.NextDouble();

double RATIO_CHANCE_A = 0.10;
double RATIO_CHANCE_B = 0.30;
double RATIO_CHANCE_C = 0.60;
double RATIO_TOTAL = RATIO_CHANCE_A + RATIO_CHANCE_B + RATIO_CHANCE_C;


if ( number < RATIO_CHANCE_A ) // A -> 10%
{
do_something1();
}
else if ( number < RATIO_CHANCE_B + RATIO_CHANCE_A ) // B -> 30%
{
do_something2();
}
else if ( number < RATIO_TOTAL ) // C -> 60%
{
do_something3();
}

【讨论】:

  • RATIO_CHANCE_B 也有同样的问题。确保它是A + B,否则你的机会只有AB 之间的差值B
  • 我的回答有两个小问题,首先是您应该在技术上使用&lt; 而不是&lt;=(例如,number 永远不会等于 1.0),其次是在维持这些比率时,您需要确保它们的总和始终恰好为 1。否则,您的概率将不准确,或者如果 RATIO_TOTAL &lt; 1.0 您可能偶尔会发现由于最终的 if 而没有任何反应声明
猜你喜欢
  • 1970-01-01
  • 2010-12-18
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 2013-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多