代码之家  ›  专栏  ›  技术社区  ›  ES2018

在两个和等于给定数字的值之间得到n个不同的随机数

  •  3
  • ES2018  · 技术社区  · 6 年前

    我想在一个范围内找到不同的随机数,其和等于给定的数。

    注意:我在stackoverflow中发现了类似的问题,但是它们并不能完全解决这个问题(即它们不考虑范围的负下限)。

    如果我想要我的随机数之和等于1,我只需要生成所需的随机数,计算和并将它们除以和;但是这里我需要一些不同的东西;我需要我的随机数加起来不等于1,仍然是我的随机数。ERS必须在给定范围内。

    示例:我需要30个不同的随机数(非整数),介于-50和50之间,其中30个生成的数字的和必须等于300;我在下面编写了代码,但是当n大于范围(上限-下限)时,它将不起作用,函数可以返回超出范围[下限-上限]的数字。是否有助于改进当前的解决方案?

    static void Main(string[] args)
    {
        var listWeights = GetRandomNumbersWithConstraints(30, 50, -50, 300);
    }
    
    private static List<double> GetRandomNumbersWithConstraints(int n, int upperLimit, int lowerLimit, int sum)
    {
        if (upperLimit <= lowerLimit || n < 1)
            throw new ArgumentOutOfRangeException();
    
        Random rand = new Random(Guid.NewGuid().GetHashCode());
        List<double> weight = new List<double>();
    
        for (int k = 0; k < n; k++)
        {
            //multiply by rand.NextDouble() to avoid duplicates
            double temp = (double)rand.Next(lowerLimit, upperLimit) * rand.NextDouble();
    
            if (weight.Contains(temp))
                k--;
            else
                weight.Add(temp);
        }
    
        //divide each element by the sum
        weight = weight.ConvertAll<double>(x => x / weight.Sum());  //here the sum of my weight will be 1 
    
        return weight.ConvertAll<double>(x => x * sum);
    }
    

    编辑-澄清

    运行当前代码将生成以下30个数字,总计300个。但是这些数字不在-50和50之间

    -4.425315699
    67.70219958
    82.08592061
    46.54014109
    71.20352208
    -9.554070146
    37.65032717
    -75.77280868
    24.68786878
    30.89874589
    142.0796933
    -1.964407284
    9.831226893
    -15.21652248
    6.479463312
    49.61283063
    118.1853036
    -28.35462683
    49.82661159
    -65.82706541
    -29.6865969
    -54.5134262
    -56.04708803
    -84.63783048
    -3.18402453
    -13.97935982
    -44.54265204
    112.774348
    -2.911427266
    -58.94098071
    
    3 回复  |  直到 6 年前
        1
  •  2
  •   Severin Pappadeux    6 年前
    }

    更新

    通常,当人们问这样的问题时,有一个隐含的假设/要求——所有随机数都应该以相同的方式分布。这意味着,如果我从抽样数组中为索引为0的项绘制边际概率密度函数(pdf),我将得到与我为数组中最后一项绘制边际概率密度函数相同的分布。人们通常对随机数组进行抽样,然后将其传递给其他例程来做一些有趣的事情。如果项目0的边际PDF与上一个索引项目的边际PDF不同,则仅恢复数组将产生与使用此类随机值的代码截然不同的结果。

    在这里,我使用我的采样程序,绘制了原始条件下([-50…50]和=300)项目0和最后一个项目(29)的随机数分布。看起来很像,不是吗?

    好的,这是您的采样程序的图片,相同的原始条件([-50…50]和=300),相同的样本数

    更新II

    用户应该检查采样例程的返回值,如果(并且仅当)返回值为真,则接受并使用采样数组。这是接受/拒绝方法。如图所示,下面是用于柱状图样本的代码:

    int[]hh=new int[100];//已分配柱状图
    
    
    

    AB

    MathDotNet

    using System;
    
    using MathNet.Numerics.Distributions;
    using MathNet.Numerics.Random;
    
    class Program
    {
        static void SampleDirichlet(double alpha, double[] rn)
        {
            if (rn == null)
                throw new ArgumentException("SampleDirichlet:: Results placeholder is null");
    
            if (alpha <= 0.0)
                throw new ArgumentException($"SampleDirichlet:: alpha {alpha} is non-positive");
    
            int n = rn.Length;
            if (n == 0)
                throw new ArgumentException("SampleDirichlet:: Results placeholder is of zero size");
    
            var gamma = new Gamma(alpha, 1.0);
    
            double sum = 0.0;
            for(int k = 0; k != n; ++k) {
                double v = gamma.Sample();
                sum  += v;
                rn[k] = v;
            }
    
            if (sum <= 0.0)
                throw new ApplicationException($"SampleDirichlet:: sum {sum} is non-positive");
    
            // normalize
            sum = 1.0 / sum;
            for(int k = 0; k != n; ++k) {
                rn[k] *= sum;
            }
        }
    
        static bool SampleBoundedDirichlet(double alpha, double sum, double lo, double hi, double[] rn)
        {
            if (rn == null)
                throw new ArgumentException("SampleDirichlet:: Results placeholder is null");
    
            if (alpha <= 0.0)
                throw new ArgumentException($"SampleDirichlet:: alpha {alpha} is non-positive");
    
            if (lo >= hi)
                throw new ArgumentException($"SampleDirichlet:: low {lo} is larger than high {hi}");
    
            int n = rn.Length;
            if (n == 0)
                throw new ArgumentException("SampleDirichlet:: Results placeholder is of zero size");
    
            double mean = sum / (double)n;
            if (mean < lo || mean > hi)
                throw new ArgumentException($"SampleDirichlet:: mean value {mean} is not within [{lo}...{hi}] range");
    
            SampleDirichlet(alpha, rn);
    
            bool rc = true;
            for(int k = 0; k != n; ++k) {
                double v = lo + (mean - lo)*(double)n * rn[k];
                if (v > hi)
                    rc = false;
                rn[k] = v;
            }
            return rc;
        }
    
        static void Main(string[] args)
        {
            double[] rn = new double [30];
    
            double lo = -50.0;
            double hi =  50.0;
    
            double alpha = 10.0;
    
            double sum = 300.0;
    
            for(int k = 0; k != 1_000; ++k) {
                var q = SampleBoundedDirichlet(alpha, sum, lo, hi, rn);
                Console.WriteLine($"Rng(BD), v = {q}");
                double s = 0.0;
                foreach(var r in rn) {
                    Console.WriteLine($"Rng(BD),     r = {r}");
                    s += r;
                }
                Console.WriteLine($"Rng(BD),    summa = {s}");
            }
        }
    }
    

    enter image description here

    enter image description here

            int[] hh = new int[100]; // histogram allocated
    
            var s = 1.0; // step size
            int k = 0;   // good samples counter
            for( ;; ) {
                var q = SampleBoundedDirichlet(alpha, sum, lo, hi, rn);
                if (q) // good sample, accept it
                {
                    var v = rn[0]; // any index, 0 or 29 or ....
                    var i = (int)((v - lo) / s);
                    i = System.Math.Max(i, 0);
                    i = System.Math.Min(i, hh.Length-1);
                    hh[i] += 1;
    
                    ++k;
                    if (k == 100000) // required number of good samples reached
                        break;
                }
            }
            for(k = 0; k != hh.Length; ++k)
            {
                var x = lo + (double)k * s + 0.5*s;
                var v = hh[k];
                Console.WriteLine($"{x}     {v}");
            }
    
        2
  •  0
  •   Davesoft    6 年前

        public List<double> TheThing(int qty, double lowest, double highest, double sumto)
        {
            if (highest * qty < sumto)
            {
                throw new Exception("Impossibru!");
                // heresy
                highest = sumto / 1 + (qty * 2);
                lowest = -highest;
            }
            double rangesize = (highest - lowest);
            Random r = new Random();
            List<double> ret = new List<double>();
    
            while (ret.Sum() != sumto)
            {
                if (ret.Count > 0)
                    ret.RemoveAt(0);
                while (ret.Count < qty)
                    ret.Add((r.NextDouble() * rangesize) + lowest);
            }
    
            return ret;
        }
    
        3
  •  0
  •   ES2018    6 年前

    static void Main(string[] args)
    {
        int n = 100;
        int max = 5000;
        int min = -500000;
        double finalSum = -1000;
    
        for (int i = 0; i < 5000; i++)
        {
            var listWeights = GetRandomNumbersWithConstraints(n, max, min, finalSum);
    
            Console.WriteLine("=============");
            Console.WriteLine("sum   = " + listWeights.Sum());
            Console.WriteLine("max   = " + listWeights.Max());
            Console.WriteLine("min   = " + listWeights.Min());
            Console.WriteLine("count = " + listWeights.Count());
        }
    }
    
    private static List<double> GetRandomNumbersWithConstraints(int n, int upperLimit, int lowerLimit, double finalSum, int precision = 6)
    {
        if (upperLimit <= lowerLimit || n < 1) //todo improve here
            throw new ArgumentOutOfRangeException();
    
        Random rand = new Random(Guid.NewGuid().GetHashCode());
    
        List<double> randomNumbers = new List<double>();
    
        int adj = (int)Math.Pow(10, precision);
    
        bool flag = true;
        List<double> weights = new List<double>();
        while (flag)
        {
            foreach (var d in randomNumbers.Where(x => x <= upperLimit && x >= lowerLimit).ToList())
            {
                if (!weights.Contains(d))  //only distinct
                    weights.Add(d);
            }
    
            if (weights.Count() == n && weights.Max() <= upperLimit && weights.Min() >= lowerLimit && Math.Round(weights.Sum(), precision) == finalSum)
                return weights;
    
            /* worst case - if the largest sum of the missing elements (ie we still need to find 3 elements, 
             * then the largest sum is 3*upperlimit) is smaller than (finalSum - sumOfValid)
             */
            if (((n - weights.Count()) * upperLimit < (finalSum - weights.Sum())) ||
                ((n - weights.Count()) * lowerLimit > (finalSum - weights.Sum())))
            {
                weights = weights.Where(x => x != weights.Max()).ToList();
                weights = weights.Where(x => x != weights.Min()).ToList();
            }
    
            int nValid = weights.Count();
            double sumOfValid = weights.Sum();
    
            int numberToSearch = n - nValid;
            double sum = finalSum - sumOfValid;
    
            double j = finalSum - weights.Sum();
            if (numberToSearch == 1 && (j <= upperLimit || j >= lowerLimit))
            {
                weights.Add(finalSum - weights.Sum());
            }
            else
            {
                randomNumbers.Clear();
                int min = lowerLimit;
                int max = upperLimit;
                for (int k = 0; k < numberToSearch; k++)
                {
                    randomNumbers.Add((double)rand.Next(min * adj, max * adj) / adj);
                }
    
                if (sum != 0 && randomNumbers.Sum() != 0)
                    randomNumbers = randomNumbers.ConvertAll<double>(x => x * sum / randomNumbers.Sum());
            }
        }
    
        return randomNumbers;
    }