以给定的精度计算两个数字之间的不同值的数量

Calculate number of distinct values between two numbers at a given precision

上下文:我正在构建一个随机数生成用户界面,用户可以在其中输入以下值:

问题是:我如何确保在给定的 lowerLimit/upperLimit 范围和给定的精度下,用户请求的数量不会超过可能的数量?

示例:

下限:1 上限:1.01 最大精度:3 数量:50

在此精度级别 (3) 上,在 1 和 1.01 之间有 11 个可能的值:1.000、1.001、1.002、1.003、1.004、1.005、1.006、1.007、1.008、1.009、1.100,但用户正在询问前 50 名。

在函数的一个版本中,return只有符合用户标准的不同值,我使用字典对象来存储已经生成的值,如果该值已经存在,请尝试另一个随机数,直到我找到了 X 个不同的随机数值,其中 X 是用户所需的数量。问题是,如果可能值的数量少于用户输入的数量,我的逻辑允许永无止境的循环。

虽然我可能会使用逻辑来检测失控情况,但我认为提前以某种方式计算可能的 return 值的数量以确保它是可能的是一种更好的方法。但我不明白这种逻辑。 (没有尝试任何东西,因为我想不出该怎么做)。

请注意:我确实看到了问题 Generating random, unique values C#,但没有解决我的问题的具体细节,即给定精度下可能值的数量和随后的失控情况。

private Random RandomSeed = new Random();
public double GetRandomDouble(double lowerBounds, double upperBounds, int maxPrecision)
{
    //Return a randomly-generated double between lowerBounds and upperBounds 
    //with maximum precision of maxPrecision
    double x = (RandomSeed.NextDouble() * ((upperBounds - lowerBounds))) + lowerBounds;
    return Math.Round(x, maxPrecision);
}
public double[] GetRandomDoublesUnique(double lowerBounds, double upperBounds, int maxPrecision, int quantity)
{
    //This method returns an array of doubles containing randomly-generated numbers
    //between user-entered lowerBounds and upperBounds with a maximum precision of
    //maxPrecision.  The array size is capped at user-entered quantity.

    //Create Dictionary to store number values already generated so we can ensure
    //we don't have duplicates
    Dictionary<double, int> myDoubles = new Dictionary<double, int>();
    double[] returnValues = new double[quantity];
    double nextValue;
    for (int i = 0; i < quantity; i++)
    {
        nextValue = GetRandomDouble(lowerBounds, upperBounds, maxPrecision);
        if (!myDoubles.ContainsKey(nextValue))
        {
            myDoubles.Add(nextValue, i);
            returnValues[i] = nextValue;
        }
        else
        {
            i -= 1;
        }
    }
    return returnValues;
}

项目数可以通过从第一个减去最后一个来计算"position"(下面的伪代码,使用Math.Pow计算10^x):

(int)(last * 10 ^ precision) - (int)(first * 10 ^ precision)

这可能需要根据您是否需要边界以及是否将 decimal(精确)或 float/double 作为输入进行调整 - 一些 +/-1 和Math.Round 可能需要补充一些以获得所有预期值的预期结果。

获得项目数量后,基本上有两种情况

  • 有更多的选择需要结果(即 1 到 100,取 5 个随机数)- 使用您必须过滤掉重复项的代码。
  • 选择的数量接近或少于所需的结果数量(即 1 到 10,return 11 个随机数)- 预先生成所有值的列表并随机播放。

试验 "significantly more" 和 "close" 之间的边界 - 我会使用 25% 作为边界(即 1 到 100,取 76 - 使用洗牌)以避免接近结束时过度退休(这是 slowness/infinite 重试基本方法的确切原因)。

shuffle 的正确实现在 Randomize a List<T> (check out similar posts like Generating random, unique values C# 中以供更多讨论)。

最简单的方法可能是将值乘以 10 ^ 精度然后减去

int lowerInt = (int)(lower * (decimal)Math.Pow(10, precision));
int higherInt = (int)(higher * (decimal)Math.Pow(10, precision));
int possibleValues = higherInt - lowerInt + 1

我觉得要求用户提前知道有多少个可能的值会违背您项目的目的,因为这似乎是他们首先使用此功能的目的。我假设该要求只是为了缓解您遇到的技术问题。您现在可以将循环更改为此

for (int i = 0; i < possibleValues; i++)

这是基于 Josh Williard 的回答的有效方法。

public double[] GetRandomDoublesUnique(double lowerBounds, double upperBounds, int maxPrecision, int quantity)
    {
        if (lowerBounds >= upperBounds)
        {
            throw new Exception("Error in GetRandomDoublesUnique is: LowerBounds is greater than UpperBounds!");
        }
        //These next few lines are for the purpose of determining the maximum possible number of return values
        //possibleValues is populated to prevent a runaway condition that could occurs if the 
        //max possible values--at the given precision level--is less than the user-selected quantity.
        //i.e. if user selects 1 to 1.01, precision of 3, and quantity of 50, there would be a problem
        // if we didn't limit loop to the 11 possible values at precision of 3:  
        //1.000, 1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009, 1.010

        int lowerInt = (int)(lowerBounds * (double)Math.Pow(10, maxPrecision));
        int higherInt = (int)(upperBounds * (double)Math.Pow(10, maxPrecision));
        int possibleValues = higherInt - lowerInt + 1;

        //Create Dictionary to store number values already generated so we can ensure
        //we don't have duplicates
        Dictionary<double, int> myDoubles = new Dictionary<double, int>();
        double[] returnValues = new double[(quantity>possibleValues?possibleValues:quantity)];
        double NextValue;
        //Iterate through and generate values--limiting to both the user-selected quantity and # of possible values
        for (int i = 0; (i < quantity)&&(i<possibleValues); i++)
        {
            NextValue = GetRandomDouble(lowerBounds, upperBounds, maxPrecision);
            if (!myDoubles.ContainsKey(NextValue))
            {
                myDoubles.Add(NextValue, i);
                returnValues[i] = NextValue;
            }
            else
            {
                i -= 1;
            }
        }
        return returnValues;
    }