web-dev-qa-db-fra.com

Algorithme pour générer n nombres aléatoires entre a et b quelle résume jusqu'à x

Ce problème semblait être quelque chose qui devrait être résolutable, mais quelques lignes de code.

Malheureusement, une fois que j'ai réellement commencé à écrire la chose, j'ai réalisé que ce n'est pas aussi simple que cela sonne.

Ce dont j'ai besoin, c'est un ensemble de nombres aléatoires x, chacun d'entre eux se situe entre A et B et ils totalisent tous les variables exactes pour le problème que je suis confronté à être encore plus simple: j'ai besoin de 5 chiffres, entre - 1 et 1 (note: ce sont des nombres rationnels (point flottant)), qui ajoutent jusqu'à 1.

Mes initiales "quelques lignes de code, devraient être faciles" L'approche était de randomiser 4 chiffres compris entre -1 et 1 (qui est assez simple), puis effectuez la dernière 1-(sum of previous numbers). Cela s'est rapidement avéré faux, car le dernier numéro pouvait aussi bien être supérieur à 1 ou inférieur à -1.

Quel serait le meilleur moyen d'aborder ce problème?

Ps. Juste pour référence: J'utilise C #, mais je ne pense pas que cela compte importe. J'ai du mal à créer une solution assez bonne pour le problème dans ma tête.


Je voulais aussi fournir ma solution actuelle au problème, mais rappelez-vous qu'il est assez imparfait et qu'il a été créé comme solution rapide au problème initial!

  • Générer 4 nombres aléatoires entre -1 et 1
  • Créez un numéro "final" X=SUM(previousNumbers)
  • Si le nombre final est> 1 ou <-1, alors: [.____]
    • Obtenez le "montant" sur 1/sous -1 et faites le dernier numéro A 1/-1
    • Trouvez un autre numéro qui peut accepter ce montant et toujours à l'intérieur des crochets
    • Si aucun numéro ne peut prendre le montant (il est trop grand/trop petit) puis divisez le montant en deux et réessayez pour chaque moitié
  • Randomiser l'ordre des nombres générés et les renvoyer

Cela fonctionne en ce sens que cet algorithme génère 5 chiffres entre -1 et 1 et leur somme est 1. Cependant, l'inconvénient est que l'un des nombres générés est un 1 (ou -1), ce qui ne se sent pas. très aléatoire.

8
Shaamaan

Simple, tant que vous savez combien de personnes.

  1. Vous avez besoin de n chiffres appelés v1 sur vn. La somme requise est S.
  2. Générer n nombres aléatoires (dans n'importe quelle plage commode). Ils sont r1 à rn.
  3. Calculer leur somme comme SR.
  4. Échelle chaque numéro afin que vn = Rn * s/sr.

Vous pouvez produire une minuscule erreur d'arrondi, mais je doute que ce sera un problème.

Si le nombre n est censé être aléatoire, choisissez-le en premier.


Toutes mes excuses, j'ai raté l'exigence pour que les chiffres soient entre A et B. L'algorithme est exactement le même. Choisissez n des nombres aléatoires, puis les échelonnez-les sur la base d'une somme réelle et d'une somme requise. Je laisse le reste comme un détail de mise en œuvre.

4
david.pfx

Il est possible que chaque variable soit distribuée uniformément sur un intervalle, tandis que la distribution conjointe est satisfaite d'une contrainte de codimension 1. Par exemple, si vous choisissez (x, y, z) de sorte qu'il soit uniformément réparti sur une sphère de l'unité, chaque coordonnée est distribuée uniformément sur l'intervalle [-1,1]. Les coordonnées ne sont tout simplement pas indépendantes.

Même si vous abandonnez l'indépendance, il n'est pas possible pour 5 numéros uniformément distribués sur [-1,1] d'avoir une somme constante de 1. Cela est que la attente est linéaire pour toutes les variables aléatoires, non seulement pour des variables indépendantes. Si vous avez 5 variables aléatoires qui sont chaque uniformément distribuées sur [-1,1], leur somme a une valeur moyenne 0, de sorte qu'il ne peut donc pas être la constante 1.

Certaines autres réponses suggèrent de choisir les premiers chiffres à être uniformes sur [-1,1], puis fixez les derniers chiffres. Cela abandonne généralement la symétrie entre les nombres. Vous serez peut-être en mesure de dire quels numéros ont été générés sans contraintes et qui ont été utilisés pour effectuer la somme en forme, car le 5ème numéro peut être dans [-1,1], mais il pourrait ne pas avoir une distribution uniforme.

Au lieu de cela, vous pouvez prendre une distribution conditionnelle. Parce que le problème est symétrique, la distribution conditionnelle est symétrique. Imaginez que vous choisissiez certains Epsilon> 0, échantillon de [-1,1] 5 fois de manière indépendante, puis rejetez le 5 tuple si la somme est supérieure à celle de 1. Cela vous donne une distribution conjointe dans l'unité 5- Cube qui est concentré près de l'hyperplane de contrainte X0 + x1 + x2 + x3 + x4 = 1. Ensuite, laissez Epsilon aller à 0, et vous obtenez une distribution limitante. Depuis la probabilité que vous ayez un point sur l'avion est trop bas (mathématiquement 0, mais positif pour, disons, des flotteurs aléatoires 32 bits) pour utiliser l'échantillonnage de rejet directement, vous avez besoin d'une autre méthode de production de cette distribution.

Il est équivalent à produire {xi/2 + 1/2} uniforme sur [0,1] avec Sum 3, ou {xi/6 + 1/6} uniforme sur [0,1/3] avec somme 1. Donc, Produire 5 nombres positifs résumant à 1, puis rejeter l'échantillon (répéter si l'un d'entre eux est supérieur à 1/3. (Ensuite, multipliez-les par 6 et soustrayez 1 pour obtenir des chiffres sur [-1 , 1] Sommation à 1.) Pour produire 5 nombres positifs résumant à 1, d'une manière consiste à générer 4 nombres aléatoires sur [0,1], triez-les (Y0, Y1, Y2, Y3), ajoutez 0 et 1 à la avant et arrière, (0, y0, y1, y2, y3,1), puis prenez les différences (Y0, Y1-Y0, Y2-Y1, Y3-Y2,1-Y3). (Une autre méthode consiste à créer 5 Variables aléatoires distribuées de manière exponentielle en prenant une bourse (uniforme) et renormalise par leur somme.)

Vous devez vous méfier d'échantillonnage de rejet dans des dimensions élevées car la probabilité d'acceptation pourrait être faible, puis vous devez itérer trop de fois. La probabilité que parmi les 5 nombres résumant à 1, aucun d'entre eux ne soit au moins 1/3 peut être trouvé avec l'exclusion d'inclusion: 1-5 (2/3) ^ 4 + 10 (1/3) ^ 4 = 11/81> 1/8. Donc, le nombre moyen de 5 tuples dont vous avez besoin pour générer par cette méthode avant de trouver un sans nombre supérieur à 1/3 est inférieur à 1/3, donc en moyenne, il faut moins de 40 nombres aléatoires uniformément pour générer un 5 tuple. avec la somme 1 tirée de la distribution conditionnelle. Si les paramètres du changement de problème changent, vous préférez peut-être une méthode plus compliquée qui évite l'échantillonnage de rejet.

1
Douglas Zare

Cette question est peut-être vieille, mais voici ma solution (écrite en C #), qui est basée sur Maarten Winkels Java code:

public static double[] GenerateRandomNumbers(uint values, double minimum, double maximum, double sum, Random generator = null)
    {
        if (values == 0)
            throw new InvalidOperationException($"Cannot create list of zero numbers.");
        if (minimum * values > sum)
            throw new InvalidOperationException($"The minimum value ({minimum}) is too high.");
        if (maximum * values < sum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is too low.");
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");
        if (generator == null)
            generator = new Random();

        var numberList = new double[values];

        for (var index = 0; index < values - 1; index++)
        {
            var rest = numberList.Length - (index + 1);

            var restMinimum = minimum * rest;
            var restMaximum = maximum * rest;

            minimum = Math.Max(minimum, sum - restMaximum);
            maximum = Math.Min(maximum, sum - restMinimum);

            var newRandomValue = generator.NextDouble(minimum, maximum);
            numberList[index] = newRandomValue;
            sum -= newRandomValue;
        }

        numberList[values - 1] = sum;

        return numberList;
    }

Code pour générer des valeurs doubles aléatoires entre une valeur minimale et maximale:

public static double NextDouble(this Random generator, double minimum, double maximum)
    {
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");

        return generator.NextDouble() * (maximum - minimum) + minimum;
    }

Et voici comment il est utilisé:

var min = -1.0;
var max = 1.0;
var sum = 0.0;
uint values = 100000;
var seed = 123;
var generator = new Random(seed);

var randomNumbers = Extensions.GenerateRandomNumbers(values, min, max, sum, generator);

Debug.WriteLine($"Distinct Values: {randomNumbers.Distinct().Count()}");
Debug.WriteLine($"Min: {randomNumbers.Min()}");
Debug.WriteLine($"Max: {randomNumbers.Max()}");
Debug.WriteLine($"Average: {randomNumbers.Average()}");
Debug.WriteLine($"Median: {randomNumbers.Median()}");
Debug.WriteLine($"Sum: {randomNumbers.Sum()}");

Debug.WriteLine("\nFirst 10 values:");
randomNumbers.Take(10).ToList().ForEach(v => Debug.WriteLine(v));

Le résultat:

Distinct Values: 99800
Min: -0,999962684698385
Max: 1
Average: 0
Median: 0,00128587102577371
Sum: 0

First 10 values:
0,969113830462617
0,815630646336652
0,487091036274606
0,623283306892628
0,477558290342595
-0,903369966849391
-0,965998261219821
-0,701281160908416
-0,610592191857562
0,26017893536956

Un problème que j'ai confronté au code des Winkels était qu'il s'agissait d'une méthode récursive et de listes de grandes listes (> 30 000 numéros), le programme lancerait des exceptions Stackoverflow. C'est pourquoi je l'ai écrit comme une fonction itérative. J'ai également essayé de nettoyer et de supprimer des opérations inutiles. J'ai également ajouté de la prise en charge de l'élimination du générateur de nombres aléatoires et de la manipulation des erreurs.

Il y a toujours de la place pour l'amélioration. Je n'ai pas trop cherché à quel point cela est vraiment aléatoire, alors enquêter plus loin si vous avez besoin de faire quelque chose de scientifique.

1
Thomas

Vous pouvez commencer avec n'importe quel nombre de 5 numéros aléatoires dans la gamme, mais il vous suffit de faire attention auxquels sont négatifs et qui sont positifs.

Case (a): Tous les 5 numéros sont positifs.

Ici, vous pouvez simplement diviser par leur somme. La somme est garantie d'être supérieure à chaque numéro individuel, car chaque nombre n est positif. Alors vous avez

0 < N < 1 

pour chaque numéro individuel N, vous avez la somme normalisée égale à 1.

CASE (B): Positifs et négatifs.

D'abord normaliser les négatifs afin que leur somme soit

-1 < (Sum of normalized negatives) < 0

À ce stade, nous sommes sûrs que chaque nombre négatif est toujours dans la gamme.

-1 < N_negative < 0.

pour chaque numéro négatif individuel N_Negative.

Suivant normaliser les positifs afin que

1 - (Sum of normalized positives) = (Sum of normalized negatives)

Maintenant, nous aurions peut-être eu des difficultés à prendre un ou plusieurs des nombres positifs en dehors de la gamme. Vérifiez si cela se produit. Sinon, alors nous sommes terminés. Si tel est le cas, ensuite renormaliser les nombres positifs de manière à ce que la valeur maximale individuelle soit 1. Nous savons que la somme des positifs renormalisés sera inférieure à la somme de la première normalisation positive, mais également la somme renormalisée sera supérieure à 1. Par conséquent, nous peut renormaliser les négatifs pour que

1 - (Sum of *re*normalized positives) = (Sum of *re*normalized negatives)

Et maintenant nous sommes définitivement faits avec l'affaire B.

CAS (C): Les 5 chiffres sont négatifs.

Cela n'a pas de solution.

0
stechray

Une option pour résoudre ce problème est de construire un arbre de recherche. L'état de l'objectif sera la somme de tous les nœuds le long de votre chemin actuel égal X. Chaque nœud est une valeur différente entre la plage, de sorte que le nombre de nœuds à chaque niveau sera ABS (A) + ABS (B). Explorez tous les chemins et obtenez toutes les solutions possibles. Ensuite, choisissez-en un au hasard dans la liste des solutions.

L'espace de recherche deviendra trop grand pour calculer lorsque N et ABS (A) + ABS (B) sont grands. Vous pouvez limiter l'espace de recherche en créant des règles. Une règle simple ici serait que si le chemin contient les mêmes valeurs, mais dans un ordre différent, ils sont considérés comme les mêmes. Cela éliminerait beaucoup de chemins sur l'arbre de recherche. Vous pouvez penser beaucoup plus pour limiter l'espace de recherche, mais je vous laisserai cet exercice.

Remarque: cette solution est surchargée, mais c'est un exercice amusant.

0
Cameron McKay

Je commencerais en simplifiant le problème. Si vous avez n chiffres de [A, B], ils vont ajouter une valeur entre N * A et N * b. Il suffit de soustraire un de chaque numéro et N * A de la cible X.

Donc, sans perte de généralité, nous pouvons rechercher n chiffres entre [0, B '] qui s'ajoute à X'. Encore une fois, cela peut être simplifié en divisant X 'par B'.

Le problème résultant recherche n chiffres dans [0,1] qui ajoutent jusqu'à x "= (x-a)/(B-A).

Quelle est la distribution de ces nombres aléatoires? L'exigence qu'ils ajoutent jusqu'à X "signifie qu'ils ne peuvent pas être des distributions indépendantes. Je suppose que nous voulons la même distribution pour tous les numéros n. Sinon, c'est trivial. Si nous savons que toutes les distributions sont les mêmes, nous faisons Sachez que leur moyenne doit être x "/ n. Ce n'est pas nécessairement 1/2, nous devons donc proposer une distribution éventuellement asymétrique sur [0,1] - une distribution symétrique a une moyenne 0,5 depuis p (x) == p (1-x). Pourtant, tous les chiffres doivent être possibles si x "/ n ne sont pas 0 ou 1.

À ce stade, vous pouvez choisir librement toute distribution paramétrée qui permet des moyens dans (0,1). Une distribution exponentielle devrait fonctionner correctement.

Enfin, une fois que vous avez dessiné un numéro A(0) À partir de cette distribution, vous devez recourir et calculer une nouvelle distribution pour le prochain numéro, car vous avez maintenant besoin de trouver des numéros N-1 qui ajoutent Jusqu'à x "-a (0). Et bien sûr pour le dernier numéro, vous n'avez plus rien le choix.

0
MSalters