Obtenez n nombres aléatoires distincts entre deux valeurs dont la sum est égale à un nombre donné

Je voudrais trouver des nombres aléatoires distincts dans une plage qui se résume à un nombre donné.

Remarque: J’ai trouvé des questions similaires dans stackoverflow, mais elles ne traitent pas exactement ce problème (c’est-à-dire qu’elles ne considèrent pas une limite inférieure négative pour la plage).

Si je voulais que la sum de mon nombre aléatoire soit égale à 1, je génère simplement les nombres aléatoires requirejs, calcule la sum et divise chacun d’eux par la sum; Cependant, ici, j’ai besoin de quelque chose d’un peu différent. J’aurai besoin que mes nombres aléatoires totalisent quelque chose de différent de 1 et que mes nombres aléatoires doivent toujours se situer dans une plage donnée.

Exemple: j’ai besoin de 30 nombres aléatoires distincts (non entiers) compris entre -50 et 50, la sum des 30 nombres générés devant être égale à 300; J’ai écrit le code ci-dessous, cependant cela ne fonctionnera pas si n est beaucoup plus grand que la plage (upperLimit – lowerLimit), la fonction pourrait renvoyer des nombres hors de la plage [lowerLimit – upperLimit]. Toute aide pour améliorer la solution actuelle?

static void Main(ssortingng[] args) { var listWeights = GetRandomNumbersWithConstraints(30, 50, -50, 300); } private static List 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 weight = new List(); 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(x => x / weight.Sum()); //here the sum of my weight will be 1 return weight.ConvertAll(x => x * sum); } 

EDIT – pour clarifier

L’exécution du code actuel générera les 30 nombres suivants, qui totalisent 300. Toutefois, ces chiffres ne sont pas compris entre -50 et 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 

Ok, voici comment cela pourrait être fait

Nous utiliserons la dissortingbution de Dirichlet , qui est la dissortingbution pour les nombres aléatoires x i dans l’intervalle [0 … 1] tels que

Somme i x i = 1

Ainsi, après condition de redimensionnement linéaire pour sum serait satisfaite automatiquement. La dissortingbution de Dirichlet est paramétrée par α i , mais nous supposons que tous les RN appartiennent à la même dissortingbution marginale, il n’y a donc qu’un seul paramètre α pour chaque indice.

Pour une grande valeur raisonnable de α, la valeur moyenne des nombres aléatoires échantillonnés serait = 1 / n et la variance ~ 1 / (n * α), de sorte que α plus grand conduit à une valeur aléatoire plus proche de la moyenne.

Ok, revenons maintenant au redimensionnement,

v i = A + b * x i

Et nous devons obtenir A et B Comme @HansKe st ing l’a noté à juste titre, avec seulement deux parameters libres, nous ne pourrions satisfaire que deux contraintes, mais vous en avez trois. Nous respecterions donc ssortingctement la contrainte de limite inférieure, contrainte de sum, mais violerons parfois la contrainte de limite supérieure. Dans ce cas, nous jetons tout l’échantillon et en faisons un autre.

Encore une fois, nous avons un bouton à tourner, α étant plus grand, nous sums proches des valeurs moyennes et moins susceptibles d’atteindre la limite supérieure. Avec α = 1, je reçois rarement un bon échantillon, mais avec α = 10, je reçois près de 40% des bons échantillons. Avec α = 16, je reçois près de 80% de bons échantillons.

L’échantillonnage de Dirichlet est effectué via une dissortingbution Gamma, à l’aide du code de MathDotNet .

Code, testé avec .NET Core 2.1

 using System; using MathNet.Numerics.Dissortingbutions; 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(ssortingng[] 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}"); } } } 

METTRE À JOUR

Habituellement, lorsque les gens posent cette question, il existe une hypothèse / exigence implicite: tous les nombres aléatoires doivent être dissortingbués de la même manière. Cela signifie que si je tire la fonction de densité de probabilité marginale (PDF) pour l’élément indexé 0 du tableau échantillonné, j’obtiendra la même dissortingbution que je dessine la fonction de densité de probabilité marginale pour le dernier élément du tableau. Les gens échantillonnent généralement des tableaux aléatoires pour les transmettre à d’autres routines afin de réaliser des tâches intéressantes. Si le PDF marginal pour l’élément 0 est différent du PDF marginal pour le dernier élément indexé, le simple fait d’inverser un tableau produira un résultat très différent avec le code qui utilise de telles valeurs aléatoires.

Ici, j’ai tracé la dissortingbution de nombres aléatoires pour l’élément 0 et le dernier élément (n ° 29) pour les conditions d’origine ([- 50 … 50] sum = 300), en utilisant ma routine d’échantillonnage. Semblable, n’est-ce pas?

entrez la description de l'image ici

Ok, voici une photo de votre routine d’échantillonnage, mêmes conditions initiales ([- 50 … 50] sum = 300), même nombre d’échantillons

entrez la description de l'image ici

MISE À JOUR II

L’utilisateur est censé vérifier la valeur de retour de la routine d’échantillonnage et accepter et utiliser un tableau échantillonné si (et seulement si) la valeur de retour est true. C’est la méthode d’acceptation / rejet. À titre d’illustration, le code ci-dessous est utilisé pour l’histogramme des échantillons:

  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}"); } 

Voici. Cela va probablement durer des siècles avant de renvoyer la liste, mais ça va être conforme 🙂

  public List 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 ret = new List(); while (ret.Sum() != sumto) { if (ret.Count > 0) ret.RemoveAt(0); while (ret.Count < qty) ret.Add((r.NextDouble() * rangesize) + lowest); } return ret; } 

Je viens avec cette solution qui est rapide. Je suis sûr que cela pourrait être amélioré, mais pour le moment, il fait le travail.

n = le nombre de nombres aléatoires que je devrai trouver

Contraintes

  • les n nombres aléatoires doivent correspondre à la sum finaleSum les n nombres aléatoires

  • les n nombres aléatoires doivent être compris entre lowerLimit et upperLimit

L’idée est de supprimer de la liste initiale (qui se résume à finalSum ) des nombres aléatoires les nombres situés en dehors de la plage [ lowerLimit , upperLimit ].

Puis comptez le nombre restant de la liste (appelé nValid ) et leur sum (appelée sumOfValid ). Maintenant, recherchez de manière itérative les nombres aléatoires ( n-nValid ) dans la plage [ lowerLimit , upperLimit ] dont la sum est ( finalSum-sumOfValid )

Je l’ai testé avec plusieurs combinaisons pour les variables d’entrée (y compris une sum négative) et les résultats semblent bons.

 static void Main(ssortingng[] 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 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 randomNumbers = new List(); int adj = (int)Math.Pow(10, precision); bool flag = true; List weights = new List(); 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(x => x * sum / randomNumbers.Sum()); } } return randomNumbers; }