Étendez une plage aléatoire de 1–5 à 1–7


692

Étant donné une fonction qui produit un entier aléatoire dans la plage de 1 à 5, écrivez une fonction qui produit un entier aléatoire dans la plage de 1 à 7.

  1. Qu'est-ce qu'une solution simple?
  2. Quelle est une solution efficace pour réduire l'utilisation de la mémoire ou fonctionner sur un processeur plus lent?

Cela s'est avéré être un problème étonnamment intéressant, je pense toujours comment 1) le faire dans un temps fixe et 2) ne pas gâcher la distribution uniforme (s'il y en avait)
eugensk

Nous avons eu le même problème en choisissant un joueur sur 5 avec un dé. Nous avons lancé les dés à tour de rôle, celui qui obtient le score maximum est choisi. L'uniformité a été atteinte, mais pas la constance du temps :)
eugensk

Aurais-je un vote négatif si j'ai posté une réponse disant que le problème ne vous oblige pas à utiliser la fonction donnée et à en écrire une qui renvoie 1-7 au hasard?
Docteur Blue

Et alors 7 * rand5() / 5?
kiwixz

@kiwixz, cela produira "entre 1 et 7", mais vous n'obtiendrez pas 3 ou 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} tests de pourcentages approximatifs manuellement. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
pythonlarry

Réponses:


572

C'est équivalent à la solution d'Adam Rosenfield, mais peut être un peu plus clair pour certains lecteurs. Il suppose que rand5 () est une fonction qui renvoie un entier statistiquement aléatoire compris entre 1 et 5 inclus.

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

Comment ça marche? Pensez-y comme ceci: imaginez imprimer ce tableau à double dimension sur du papier, le clouer sur un jeu de fléchettes et lancer des fléchettes au hasard. Si vous atteignez une valeur non nulle, il s'agit d'une valeur statistiquement aléatoire entre 1 et 7, car il existe un nombre égal de valeurs non nulles parmi lesquelles choisir. Si vous frappez un zéro, continuez simplement à lancer la fléchette jusqu'à ce que vous frappiez un non-zéro. C'est ce que fait ce code: les index i et j sélectionnent au hasard un emplacement sur le jeu de fléchettes, et si nous n'obtenons pas un bon résultat, nous continuons à lancer des fléchettes.

Comme Adam l'a dit, cela peut durer éternellement dans le pire des cas, mais statistiquement, le pire des cas ne se produit jamais. :)


5
J'ai compris la logique derrière cette solution, mais je ne peux pas comprendre comment cela se traduit-il par une probabilité uniforme? Quelqu'un peut-il expliquer les mathématiques?
user1071840

6
@ user1071840 - si rand5est uniforme, chaque cellule de la valsgrille a une probabilité égale d'être sélectionnée. La grille contient exactement trois copies de chaque entier dans l'intervalle [1, 7], plus quatre zéros. Ainsi, le flux de résultats "brut" tend à un mélange uniforme de valeurs [1, 7], plus quelques zéros qui apparaissent un peu plus fréquemment que toute valeur individuelle autorisée. Mais cela n'a pas d'importance car les zéros sont supprimés, ne laissant qu'un mélange homogène de valeurs [1, 7].
Daniel Earwicker

3
Le raccourci pour comprendre le problème: si vous n'appelez rand5 () qu'une seule fois, vous n'avez que 5 résultats possibles. Il n'y a évidemment aucun moyen de transformer cela en plus de 5 résultats possibles sans ajouter plus de hasard.
Daniel Earwicker

1
La version plus longue: rand5 () ne peut avoir que les valeurs (1, 2, 3, 4, 5). Par conséquent, rand5 () * 5 ne peut avoir que les valeurs (5, 10, 15, 20, 25), ce qui n'est pas la même chose qu'une plage complète (1 ... 25). Si c'était le cas, la soustraction de 4 ferait (-3 ... 21), mais dans ce cas, cela devient (1, 6, 11, 16, 21), donc les points finaux sont corrects mais il y a quatre gros trous: ( 2..5), (7..10), (12..15), (17..21). Enfin, vous faites le mod 7 et ajoutez 1, donnant (2, 7, 5, 3, 1). Donc, ni 4 ni 6 ne se produisent jamais. Mais (voir le raccourci ci-dessus), nous savions qu'il ne pouvait y avoir que 5 nombres dans la plage résultante, donc il devait y avoir deux lacunes.
Daniel Earwicker

1
Ah, parce que nous n'avons que rand5 (), pas rand2 () :-)
gzak

353

Il n'y a pas de solution (exactement correcte) qui fonctionnera dans un laps de temps constant, car 1/7 est une décimale infinie en base 5. Une solution simple serait d'utiliser l'échantillonnage de rejet, par exemple:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

Cela a un temps d'exécution prévu de 25/21 = 1,19 itérations de la boucle, mais il y a une probabilité infiniment petite de boucler pour toujours.


7
le -1 n'est pas nécessaire si le> 21 est basculé sur> 26 b / c, peu importe où se situe la borne inférieure de i,
BCS

26
Mon point de vue sur l'explication de la raison: dites que je veux écrire un programme qui génère un flux de nombres aléatoires uniformes de 1 à 25; pour cela je retournerais juste 5 * (rand5 () - 1) + rand5 () comme dans le code de la réponse. Maintenant, si je veux construire un flux de nombres aléatoires uniformes entre 1 et 21, si j'utilise simplement le premier flux mais le filtre de manière à ce que les nombres dans [22, 25] soient rejetés, je peux également créer ce flux. Ensuite, si je prends ce flux et le filtre pour que pour chaque élément x je produise x% 7 + 1, j'ai un flux de nombres aléatoires uniformes de 1 à 7! C'est assez simple, non? : D
Paggas

6
Et vous avez raison, cela se résume à savoir si vous voulez une distribution parfaite avec le pire cas d'exécution illimité ou une distribution imparfaite avec un temps d'exécution limité. Ceci est une conséquence du fait que tous les pouvoirs 5 ne sont pas divisibles par 7, ou de manière équivalente si vous avez 5 ^ n séquences également probablement de longueur n, il n'y a aucun moyen d'attribuer à chaque séquence un nombre de 1 à 7 tel que chacun de 1..7 est également probable.
Adam Rosenfield

5
@Jules Olléon: Supposons qu'une solution fonctionnant en temps constant garantisse de ne faire que des Nappels rand5()dans le pire des cas. Ensuite, il y a 5 ^ N résultats possibles de la séquence d'appels à rand5, dont chacun a une sortie de 1-7. Donc, si vous additionnez toutes les séquences d'appels possibles dont la sortie est kpour chaque 1≤k≤7, alors la probabilité que la sortie soit kest m / 5 ^ N, où m est le nombre de telles séquences. Donc, m / 5 ^ N = 1/7, mais il n'y a pas de solution entière possible (N, m) à cette contradiction ==>.
Adam Rosenfield

4
@paxdiablo: Vous vous trompez. La chance qu'un vrai RNG génère une séquence infinie de 5 est exactement 0, en utilisant un raisonnement similaire au fait que lancer une pièce un nombre infini de fois est garanti de ne pas générer un nombre infini de têtes consécutives . Cela signifie également que la chance de ce code en boucle pour toujours est exactement de 0 (bien qu'il y ait une chance positive qu'il boucle en tout nombre arbitraire d'itérations).
BlueRaja - Danny Pflughoeft

153

Je voudrais ajouter une autre réponse, en plus de ma première réponse . Cette réponse tente de minimiser le nombre d'appels vers rand5()chaque appel rand7(), afin de maximiser l'utilisation de l'aléatoire. Autrement dit, si vous considérez le hasard comme une ressource précieuse, nous voulons en utiliser autant que possible, sans jeter de bits aléatoires. Cette réponse présente également certaines similitudes avec la logique présentée dans la réponse d'Ivan .

L' entropie d'une variable aléatoire est une quantité bien définie. Pour une variable aléatoire qui prend N états avec des probabilités égales (une distribution uniforme), l'entropie est log 2 N. Ainsi, elle rand5()a environ 2,32193 bits d'entropie et rand7()environ 2,80735 bits d'entropie. Si nous espérons maximiser notre utilisation de l'aléatoire, nous devons utiliser tous les 2,32193 bits d'entropie de chaque appel à rand5(), et les appliquer pour générer 2,80735 bits d'entropie nécessaires pour chaque appel à rand7(). La limite fondamentale est donc que nous ne pouvons pas faire mieux que log (7) / log (5) = 1,20906 appels rand5()par appel à rand7().

Notes annexes: tous les logarithmes de cette réponse seront en base 2, sauf indication contraire. rand5()sera supposé renvoyer des nombres dans la plage [0, 4], et rand7()sera supposé renvoyer des nombres dans la plage [0, 6]. Ajuster les plages respectivement à [1, 5] et [1, 7] est trivial.

Alors comment le fait-on? Nous générons un nombre réel aléatoire infiniment précis entre 0 et 1 (imaginez pour le moment que nous puissions réellement calculer et stocker un tel nombre infiniment précis - nous le corrigerons plus tard). On peut générer un tel nombre en générant ses chiffres en base 5: on choisit le nombre aléatoire 0. a1 a2 a3 ..., où chaque chiffre a iest choisi par un appel à rand5(). Par exemple, si notre RNG choisissait a i= 1 pour tous i, alors en ignorant le fait que ce n'est pas très aléatoire, cela correspondrait au nombre réel 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (somme d'une série géométrique).

Ok, nous avons donc choisi un nombre réel aléatoire entre 0 et 1. Je prétends maintenant qu'un tel nombre aléatoire est uniformément distribué. Intuitivement, cela est facile à comprendre, car chaque chiffre a été choisi uniformément et le nombre est infiniment précis. Cependant, une preuve formelle de cela est un peu plus impliquée, puisque maintenant nous avons affaire à une distribution continue au lieu d'une distribution discrète, nous devons donc prouver que la probabilité que notre nombre se situe dans un intervalle [ a, b] est égale à la longueur de cet intervalle, b - a. La preuve est laissée en exercice au lecteur =).

Maintenant que nous avons un nombre réel aléatoire sélectionné uniformément dans la plage [0, 1], nous devons le convertir en une série de nombres uniformément aléatoires dans la plage [0, 6] pour générer la sortie de rand7(). Comment faisons-nous cela? Juste l'inverse de ce que nous venons de faire - nous le convertissons en une décimale infiniment précise en base 7, puis chaque chiffre de base 7 correspondra à une sortie derand7() .

Prenant l'exemple du précédent, si notre rand5() produit un flux infini de 1, alors notre nombre réel aléatoire sera 1/4. En convertissant 1/4 en base 7, nous obtenons la décimale infinie 0,15151515 ..., nous produirons donc en sortie 1, 5, 1, 5, 1, 5, etc.

Ok, nous avons donc l'idée principale, mais il nous reste deux problèmes: nous ne pouvons pas réellement calculer ou stocker un nombre réel infiniment précis, alors comment pouvons-nous en traiter seulement une partie finie? Deuxièmement, comment pouvons-nous réellement le convertir en base 7?

Une façon de convertir un nombre compris entre 0 et 1 en base 7 est la suivante:

  1. Multipliez par 7
  2. La partie intégrante du résultat est le prochain chiffre de base 7
  3. Soustrayez la partie intégrale, ne laissant que la partie fractionnaire
  4. Aller à l'étape 1

Pour faire face au problème de la précision infinie, nous calculons un résultat partiel, et nous stockons également une limite supérieure sur ce que pourrait être le résultat. Autrement dit, supposons que nous ayons appelé rand5()deux fois et qu'il soit retourné 1 fois. Le nombre que nous avons généré jusqu'à présent est de 0,11 (base 5). Quel que soit le reste de la série infinie d'appels à rand5()produire, le nombre réel aléatoire que nous générons ne sera jamais supérieur à 0,12: il est toujours vrai que 0,11 ≤ 0,11xyz ... <0,12.

Donc, en gardant une trace du nombre actuel jusqu'à présent, et de la valeur maximale qu'il pourrait jamais prendre, nous convertissons les deux nombres en base 7. S'ils s'accordent sur les premiers kchiffres, alors nous pouvons sortir en toute sécurité les kchiffres suivants - quel que soit le flux infini de chiffres de base 5 sont, ils n'affecteront jamais les prochains kchiffres de la représentation de base 7!

Et c'est l'algorithme - pour générer la prochaine sortie de rand7(), nous générons seulement autant de chiffres rand5()que nécessaire pour nous assurer que nous connaissons avec certitude la valeur du chiffre suivant dans la conversion du nombre réel aléatoire en base 7. Voici une implémentation Python, avec un harnais de test:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

Notez que rand7_gen()renvoie un générateur, car il a un état interne impliquant la conversion du nombre en base 7. Le faisceau de test appelle next(r7)10 000 fois pour produire 10 000 nombres aléatoires, puis il mesure leur distribution. Seules les mathématiques entières sont utilisées, donc les résultats sont exactement corrects.

Notez également que les chiffres ici deviennent très gros, très rapides. Les pouvoirs de 5 et 7 augmentent rapidement. Par conséquent, les performances commenceront à se dégrader sensiblement après avoir généré de nombreux nombres aléatoires, en raison de l'arithmétique du bignum. Mais rappelez-vous ici, mon objectif était de maximiser l'utilisation de bits aléatoires, pas de maximiser les performances (bien que ce soit un objectif secondaire).

En une seule fois, j'ai effectué 12091 appels vers rand5()10000 appels vers rand7(), atteignant le minimum d' appels log (7) / log (5) en moyenne à 4 chiffres significatifs, et la sortie résultante était uniforme.

Afin de porter ce code dans un langage qui n'a pas de nombres entiers arbitrairement grands intégrés, vous devrez limiter les valeurs de pow5et pow7à la valeur maximale de votre type intégral natif - si elles deviennent trop grandes, puis réinitialisez tout et recommencer. Cela augmentera très légèrement le nombre moyen d'appels rand5()par appel rand7(), mais nous espérons qu'il ne devrait pas augmenter trop, même pour les entiers 32 ou 64 bits.


7
+1 pour une réponse vraiment intéressante. Serait-il possible, plutôt que de réinitialiser à une certaine valeur, de simplement décaler les bits qui ont été utilisés et de déplacer les autres bits vers le haut, et de ne garder essentiellement que les bits qui vont être utilisés? Ou est-ce que je manque quelque chose?
Chris Lutz

1
Je ne suis pas sûr à 100%, mais je crois que si vous faisiez cela, vous fausseriez légèrement la distribution (bien que je doute qu'un tel biais soit mesurable sans des milliards d'essais).
Adam Rosenfield

FTW! J'ai essayé de rendre les bignums plus petits mais cela ne peut pas être fait car aucune puissance de 5 n'a de facteurs communs avec une puissance de 7! Aussi, bonne utilisation du mot-clé yield. Très bien fait.
Eyal

2
Très agréable! Pouvons-nous conserver l'entropie supplémentaire sans croître? L'astuce consiste à remarquer que les bornes supérieure et inférieure sont toujours des nombres rationnels. Nous pouvons les additionner, les soustraire et les multiplier sans perdre en précision. Si nous faisons tout en base 35, nous y sommes presque. Le reste (multipliant par sept et conservant la partie fractionnaire) est laissé comme exercice.
Ian

@adam Vous devez vous référer à "plafonner les valeurs de pow5 et pow7 à la valeur maximale de votre type intégral natif". J'appuie votre conviction que cela faussera la distribution, du moins si elle est naïve.
catalyseur

36

(J'ai volé la réponse d'Adam Rosenfeld et l' ai fait courir environ 7% plus rapidement.)

Supposons que rand5 () retourne l'un de {0,1,2,3,4} avec une distribution égale et le but est de retourner {0,1,2,3,4,5,6} avec une distribution égale.

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

Nous gardons une trace de la plus grande valeur que la boucle peut faire dans la variable max. Si le reult jusqu'à présent se situe entre max% 7 et max-1, le résultat sera uniformément diffusé dans cette plage. Sinon, nous utilisons le reste, qui est aléatoire entre 0 et max% 7-1, et un autre appel à rand () pour faire un nouveau numéro et un nouveau max. Puis nous recommençons.

Edit: attendez le nombre de fois d'appeler rand5 () est x dans cette équation:

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()

2
Résultats catalogués dans 1 000 000 d'essais: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465. Comme vous pouvez le voir, la distribution fait défaut en ce qui concerne les chances d'obtenir un 1.
Robert K

2
@The Wicked Flea: Je pense que vous vous trompez. Êtes-vous sûr que l'entrée rand5 () que vous utilisiez pour votre test a produit 0-4 au lieu de 1-5, comme spécifié dans cette solution?
Adam Rosenfield

5
l'ajout de nombres uniformément distribués n'entraîne pas un nombre uniformément distribué. En fait, vous n'avez qu'à additionner 6 de ces variables uniformément réparties pour obtenir une approximation raisonnable d'une distribution normale.
Mitch Wheat

2
@MitchWheat - L'ajout de deux entiers uniformément distribués se traduit en fait par un entier aléatoire uniformément réparti à condition que chaque somme possible puisse être générée exactement d'une manière. Cela se trouve être le cas dans l'expression 5 * rand5() + rand5().
Ted Hopp

28

Algorithme:

7 peut être représenté dans une séquence de 3 bits

Utilisez rand (5) pour remplir aléatoirement chaque bit avec 0 ou 1.
Par exemple: appelez rand (5) et

si le résultat est 1 ou 2, remplissez le bit avec 0
si le résultat est 4 ou 5, remplissez le bit avec 1
si le résultat est 3, puis ignorez et recommencez (rejet)

De cette façon, nous pouvons remplir 3 bits au hasard avec 0/1 et ainsi obtenir un nombre de 1-7.

EDIT: Cela semble être la réponse la plus simple et la plus efficace, alors voici un code pour cela:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}

1
Il y a toujours le faible spectre du problème d'arrêt, car un mauvais générateur de nombres aléatoires pourrait simplement générer beaucoup de trois à un moment donné.
Alex North-Keys

"si le résultat est 1 ou 2, remplissez le bit avec 0 si le résultat est 4 ou 5, remplissez le bit avec 1" Quelle est la logique selon laquelle 1,2,4,5 ont été acceptés et 3 a été rejeté? Pouvez-vous expliquer cela?
gkns

@gkns Il n'y a pas de logique, vous pourriez avoir 1 et 2 remplissage moyen avec 0 bit et 3 et 4 remplissage moyen avec 1. L'important est que chaque option a 50% de chances de se produire, garantissant ainsi que le caractère aléatoire de votre fonction est au moins aussi aléatoire que la fonction rand (5) d'origine. C'est une excellente solution!
Mo Beigi

Ce n'est ni simple ni efficace. Le nombre de cals à random_5 par random_7 est au mieux 3 généralement plus. Les autres solutions sur cette page sont plus proches de la meilleure qui soit autour de 2.2.
Eyal

1
Peu importe, j'ai raté la partie "while returnValue == 0"
NicholasFolk

19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}

2
Une solution correcte, faisant une moyenne de 30/7 = 4,29 appels à rand5 () par appel à rand7 ().
Adam Rosenfield,

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

Edit: Cela ne fonctionne pas tout à fait. Il est d'environ 2 parties sur 1000 (en supposant un parfait rand5). Les seaux obtiennent:

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

En passant à une somme de

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

semble gagner un ordre de grandeur pour chaque 2 ajouté

BTW: le tableau des erreurs ci-dessus n'a pas été généré par échantillonnage mais par la relation de récurrence suivante:

p[x,n]est le nombre de façons dont les appels output=xpeuvent arriver .nrand5

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]

8
Ce n'est pas une distribution uniforme. C'est très proche de l'uniforme, mais pas parfaitement uniforme.
Adam Rosenfield,

Ah! Dés et 7. Si vous voulez dire que je me trompe, vous ne devriez pas laisser la preuve comme un exercice pour le lecteur.
BCS

45
La preuve que ce n'est pas uniforme est simple: il y a 5 ^ 7 façons possibles pour le hasard, et comme 5 ^ 7 n'est pas un multiple de 7, il n'est pas possible que les 7 sommes soient également probables. (Fondamentalement, cela se résume à 7 étant relativement premier à 5, ou de manière équivalente 1/7 n'étant pas une décimale terminale en base 5.) En fait, ce n'est même pas le "plus uniforme" possible sous cette contrainte: le calcul direct montre celui du 5 ^ 7 = 78125 sommes, le nombre de fois où vous obtenez les valeurs 1 à 7 est {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}.
ShreevatsaR le

@ShreevatsaR Et si au lieu de prendre la somme de rand5 () sept fois, nous l'avons fait 5 * 7 prend - cela ne fonctionnerait-il pas? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba

4
@KristianAntonsen: Combien de fois vous faites rand5 (), vous n'obtiendrez pas une distribution uniforme. Si vous le faites N fois, il y a 5 ^ N sorties possibles, ce qui n'est pas divisible par 7. (Si vous le faites 35 fois, il y en a 5 ^ 35, pas 35 ^ 7.) Vous vous rapprocherez de plus en plus de uniforme le plus grand nombre d'appels que vous utilisez (et il peut être n'importe quel nombre, ne doit pas être divisible par 7), mais à mon humble avis au lieu d'utiliser un très grand nombre d'appels à rand (), vous pouvez aussi utiliser le probabiliste algorithme dans les premières réponses, qui donne une distribution uniforme exacte et dont le nombre prévu d'appels à rand () est petit.
ShreevatsaR

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
Une solution correcte, faisant une moyenne de 30/7 = 4,29 appels à rand5 () par appel à rand7 ().
Adam Rosenfield

3
A besoin d'être décalage vers la gauche pour l'algorithme de travail:ans += (r < 3) << i
woolfie

13

Ce qui suit produit une distribution uniforme sur {1, 2, 3, 4, 5, 6, 7} en utilisant un générateur de nombres aléatoires produisant une distribution uniforme sur {1, 2, 3, 4, 5}. Le code est désordonné, mais la logique est claire.

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    

2
Une solution correcte (qui vous place loin devant la courbe), mais pas très efficace. Cela fait une moyenne de 25/6 = 4,17 appels à random_5_mod_2 par tirage au sort, pour une moyenne totale de 100/7 = 14,3 appels à random_5 () par appel à random_7 ().
Adam Rosenfield

L'avantage de cette solution par rapport aux autres est qu'elle peut être facilement étendue pour produire toute autre gamme uniformément répartie. Sélectionnez simplement au hasard chacun des bits, en relançant sur des valeurs invalides (comme la valeur 0 dans notre solution actuelle qui produit 8 nombres).
DenTheMan

1
boucles infinies possibles, etc.
robermorales

1
@robermorales: Extrêmement improbable.
jason

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

Contrairement à la solution choisie, l'algorithme s'exécutera en temps constant. Il fait cependant 2 appels de plus vers rand5 que le temps d'exécution moyen de la solution choisie.

Notez que ce générateur n'est pas parfait (le nombre 0 a 0,0064% de chances en plus que tout autre nombre), mais pour la plupart des raisons pratiques, la garantie d'un temps constant l'emporte probablement sur cette imprécision.

Explication

Cette solution est dérivée du fait que le nombre 15,624 est divisible par 7 et donc si nous pouvons générer de manière aléatoire et uniforme des nombres de 0 à 15,624 puis prendre le mod 7, nous pouvons obtenir un générateur de rand7 presque uniforme. Les nombres de 0 à 15 624 peuvent être générés uniformément en roulant 6 fois rand5 et en les utilisant pour former les chiffres d'un nombre de base 5 comme suit:

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

Les propriétés du mod 7 nous permettent cependant de simplifier un peu l'équation:

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

Donc

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

devient

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

Théorie

Le nombre 15624 n'a pas été choisi au hasard, mais peut être découvert en utilisant le petit théorème de fermat, qui stipule que si p est un nombre premier,

a^(p-1) = 1 mod p

Cela nous donne donc,

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 est égal à

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

Il s'agit d'un nombre sous forme de base 5 et nous pouvons donc voir que cette méthode peut être utilisée pour passer de n'importe quel générateur de nombres aléatoires à tout autre générateur de nombres aléatoires. Bien qu'un petit biais vers 0 soit toujours introduit lors de l'utilisation de l'exposant p-1.

Pour généraliser cette approche et pour être plus précis, nous pouvons avoir une fonction comme celle-ci:

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)

1
Ce générateur est précis, mais pas parfaitement uniforme. Pour voir cela, considérons le fait qu'un générateur uniforme dans [0,15624] a 15625 résultats possibles, qui n'est pas divisible par 7. Cela introduit un biais au nombre 0 (qui a 2233/15625 de chance, et les autres juste 2232/15625). Après tout, tout en utilisant le petit théorème de Fermat peut sembler correct à première vue, il dit que (5 ^ 6)% 7 = 1, et non (5 ^ 6)% 7 = 0. Ce dernier est évidemment impossible pour tout exposant car 5 et 7 sont tous les deux des nombres premiers. Je pense que c'est toujours une solution acceptable, et j'ai modifié votre article pour refléter cela.
aviateur

12

Les problèmes de devoirs sont-ils autorisés ici?

Cette fonction fait des calculs bruts de "base 5" pour générer un nombre compris entre 0 et 6.

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
Une solution correcte (qui vous place loin devant la courbe), mais pas très efficace. Cela fait en moyenne 5 appels à rnd5 () pour chaque appel à rnd7 ().
Adam Rosenfield

besoin de quelques explications supplémentaires
Barry

1
@Barry - Tout d'abord, vous ne pouvez pas simplement additionner deux nombres aléatoires ensemble, vous n'obtenez pas une solution linéaire (pensez à une paire de dés). Considérons maintenant "Base 5": 00, 01, 02, 03, 04, 10, 11. Ce 0-6 dans la base 5. Donc, nous avons simplement besoin de générer 2 chiffres du nombre de base 5, et de les additionner jusqu'à ce que nous en obtenir un qui est dans la plage. C'est ce que fait le r2 * 5 + r1. La boucle r2> 1 est là parce que nous ne voudrions jamais un chiffre élevé de> 1.
Will Hartung

Cette solution ne génère pas de distribution uniforme. Les nombres 1 et 7 ne peuvent être générés que dans un sens, mais 2 à 6 peuvent chacun être générés de deux manières: avec r1 égal au nombre moins 1 et r2 égal 0 ou avec r1 égal au nombre moins 2 et r2 égal à 1. Ainsi, 2 à 6 seront retournés en moyenne deux fois plus souvent que 1 ou 7.
Ted Hopp

12

Si nous considérons la contrainte supplémentaire d'essayer de donner la réponse la plus efficace, c'est-à-dire celle qui donne un flux d'entrée I, des entiers uniformément distribués de longueur mde 1-5 sort un flux O, des entiers uniformément distribués de 1-7 de la plus longue longueur relative à m, disons L(m).

La manière la plus simple d'analyser cela est de traiter les flux I et Ocomme des nombres à 5 et 7 espaces respectivement. Ceci est réalisé par l'idée de la réponse principale de prendre le flux a1, a2, a3,... -> a1+5*a2+5^2*a3+..et de même pour le flux O.

Ensuite, si nous prenons une section du flux d'entrée de longueur m choose n s.t. 5^m-7^n=cc>0et est aussi petite que possible. Ensuite, il y a une carte uniforme du flux d'entrée de longueur m aux entiers de 1à 5^met une autre carte uniforme des entiers de 1 au 7^nflux de sortie de longueur n où nous pouvons avoir à perdre quelques cas du flux d'entrée lorsque l'entier mappé dépasse 7^n.

Cela donne donc une valeur L(m)de autour m (log5/log7)qui est approximativement .82m.

La difficulté avec l'analyse ci-dessus est l'équation 5^m-7^n=cqui n'est pas facile à résoudre exactement et le cas où la valeur uniforme de 1to 5^mdépasse 7^net nous perdons en efficacité.

La question est de savoir jusqu'à quel point la meilleure valeur possible de m (log5 / log7) peut être atteinte. Par exemple, lorsque ce nombre se rapproche d'un entier, pouvons-nous trouver un moyen d'atteindre ce nombre entier exact de valeurs de sortie?

Si 5^m-7^n=censuite, à partir du flux d'entrée, nous générons effectivement un nombre aléatoire uniforme de 0à (5^m)-1et n'utilisons aucune valeur supérieure à 7^n. Cependant, ces valeurs peuvent être récupérées et réutilisées. Ils génèrent efficacement une séquence uniforme de nombres de 1 à 5^m-7^n. Nous pouvons donc essayer de les utiliser et les convertir en nombres à 7 zones afin de créer plus de valeurs de sortie.

Si nous laissons T7(X)la longueur moyenne de la séquence de sortie des random(1-7)entiers dérivée d'une entrée uniforme de taille X, et en supposant cela 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Alors T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)puisque nous avons une longueur sans séquence avec probabilité 7 ^ n0 / 5 ^ m avec un résidu de longueur 5^m-7^n0avec probabilité (5^m-7^n0)/5^m).

Si nous continuons à remplacer, nous obtenons:

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

Par conséquent

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

Une autre façon de le dire est:

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

Le meilleur cas possible est mon cas ci-dessus où 5^m=7^n+s, oùs<7 .

Puis T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)comme avant.

Le pire des cas est celui où l'on ne trouve que k et st 5 ^ m = kx7 + s.

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

D'autres cas se situent quelque part entre les deux. Il serait intéressant de voir dans quelle mesure nous pouvons faire pour de très grands m, c'est-à-dire dans quelle mesure pouvons-nous obtenir le terme d'erreur:

T7(5^m) = m (Log5/Log7)+e(m)

Cela semble impossible à réaliser e(m) = o(1)en général, mais j'espère que nous pourrons le prouver e(m)=o(m).

Le tout repose alors sur la distribution des 7 chiffres de 5^mpour différentes valeurs de m.

Je suis sûr qu'il y a beaucoup de théories qui couvrent ce sujet.


+2 (si je pouvais) - c'était la seule bonne réponse (par opposition à simplement adéquate). Vous avez la deuxième meilleure réponse qui s'adaptera aux entiers 32 bits.
Rex Kerr

10

Voici une implémentation Python fonctionnelle de la réponse d' Adam .

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

J'aime lancer des algorithmes que je regarde dans Python afin que je puisse jouer avec eux, pensais que je le posterais ici dans l'espoir qu'il soit utile à quelqu'un là-bas, pas qu'il ait fallu longtemps pour lancer ensemble.


Non, c'est assez différent de ma réponse. Vous bouclez 21 fois et annulez les résultats des 20 premières itérations. Vous utilisez également un rand4 () et un rand5 () en entrée, ce qui enfreint bien évidemment les règles d'utilisation de rand5 () uniquement. Enfin, vous produisez une distribution non uniforme.
Adam Rosenfield,

Désolé pour ça. J'étais assez fatigué quand j'ai regardé cette question, assez fatigué pour avoir mal lu votre algorithme. En fait, je l'ai jeté dans Python parce que je ne comprenais pas pourquoi vous boucliez 21 fois. Cela a beaucoup plus de sens maintenant. J'ai fait la chose random.randint (1, 4) comme raccourci mais je suppose que vous avez raison, c'est contre l'esprit de la question. J'ai corrigé le code.
James McMahon

@robermorales - Comme Adam Rosenfeld l'a expliqué dans sa réponse , chaque solution qui donne une véritable distribution uniforme sur [1, 7] impliquera une sorte de boucle d'acceptation-rejet potentiellement infinie. (Cependant, si rand5()c'est un PRNG décent, alors la boucle ne sera pas infinie car finalement elle 5*(rand5() - 1) + rand5()sera définitivement <= 21.)
Ted Hopp

10

Pourquoi ne pas faire simple?

int random7() {
  return random5() + (random5() % 3);
}

Les chances d'obtenir 1 et 7 dans cette solution sont plus faibles en raison du modulo, cependant, si vous voulez juste une solution rapide et lisible, c'est la voie à suivre.


13
Cela ne produit pas une distribution uniforme. Cela produit les nombres 0-6 avec des probabilités 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, comme on peut le vérifier en comptant les 25 résultats possibles.
Adam Rosenfield

8

En supposant que rand (n) signifie ici "entier aléatoire dans une distribution uniforme de 0 à n-1 ", voici un exemple de code utilisant randint de Python, qui a cet effet. Il utilise uniquement randint (5) et des constantes pour produire l'effet de randint (7) . Un peu idiot, en fait

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum

1
@robermorales Parce que Python n'en a pas do ... while. Il aurait pu être 1337, ou 12345, ou n'importe quel nombre> 1.
tckmn

8

La prémisse derrière la bonne réponse d'Adam Rosenfield est:

  • x = 5 ^ n (dans son cas: n = 2)
  • manipuler n appels rand5 pour obtenir un nombre y dans la plage [1, x]
  • z = ((int) (x / 7)) * 7
  • si y> z, réessayez. sinon retourne y% 7 + 1

Lorsque n est égal à 2, vous avez 4 possibilités de mise au rebut: y = {22, 23, 24, 25}. Si vous utilisez n est égal à 6, vous n'avez qu'un jetable: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Vous appelez rand5 plusieurs fois. Cependant, vous avez une chance beaucoup plus faible d'obtenir une valeur jetable (ou une boucle infinie). S'il existe un moyen d'obtenir aucune valeur de rejet possible pour y, je ne l'ai pas encore trouvé.


1
Il n'y a sans doute aucun cas sans valeurs jetables - s'il n'y avait pas de jetable, 5 ^ n et 7 ^ m auraient un facteur en commun. Mais ce sont des (pouvoirs de) nombres premiers, donc ils ne le font pas.
Rex Kerr

8

Voici ma réponse:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

C'est un peu plus compliqué que les autres, mais je pense que cela minimise les appels à rand5. Comme avec d'autres solutions, il y a une faible probabilité qu'il puisse boucler pendant une longue période.


Cela produit une distribution pas très différente des autres solutions mais présente l'inconvénient supplémentaire d'être inutilement complexe. Il souffre également de la possibilité de boucle non déterministe non déterministe, si les nombres sont vraiment aléatoires. Je pense toujours que ceux qui produisent une distribution légèrement moins uniforme (bien que toujours bien plus qu'adéquate) mais garantissent un comportement déterministe sont meilleurs.
paxdiablo

@Pax: Veuillez m'éclairer sur la façon dont cela produit une distribution non uniforme. Mon analyse du code, ainsi que mes propres tests, indiquent que cela produit une distribution uniforme. Comme nous l'avons vu précédemment, il est impossible à la fois de produire une distribution parfaitement uniforme et d'avoir une limite supérieure de temps constant garantie du temps de fonctionnement.
Adam Rosenfield,


6

Tant qu'il ne reste plus sept possibilités, choisissez un autre nombre aléatoire, qui multiplie le nombre de possibilités par cinq. En Perl:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}

votre distribution n'est pas uniforme, du moins au premier appel. En effet, il $possibilitiesfaut toujours passer à 25 pour sortir de la boucle et revenir. Donc, votre premier résultat est [0-124] % 7, qui n'est pas uniformément distribué car 125 % 7 != 0(c'est 6, en fait).
bernard paulus

6

Je n'aime pas les plages à partir de 1, donc je vais commencer à 0 :-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}

Ceci est un gagnant. Cela produit les 7 résultats avec une probabilité égale. from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown

5

Voilà, distribution uniforme et zéro appel rand5.

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

Besoin de semer au préalable.


5

Je sais qu'il a été répondu, mais est-ce que cela semble fonctionner correctement, mais je ne peux pas vous dire si cela a un biais. Mes «tests» suggèrent que c'est, au moins, raisonnable.

Adam Rosenfield aurait peut-être la gentillesse de commenter?

Mon idée (naïve?) Est la suivante:

Accumulez les rand5 jusqu'à ce qu'il y ait suffisamment de bits aléatoires pour faire un rand7. Cela prend au plus 2 rand5. Pour obtenir le nombre rand7, j'utilise la valeur cumulée mod 7.

Pour éviter que l'accumulateur ne déborde, et comme l'accumulateur est le mod 7 alors je prends le mod 7 de l'accumulateur:

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

La fonction rand7 () suit:

(Je laisse la plage de rand5 être 0-4 et rand7 est également 0-6.)

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

Edit: Ajout de résultats pour 100 millions d'essais.

Fonctions «réelles» de rand mod 5 ou 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282879 2: 14284554 3: 14288546 4: 14292388 5: 14288736 6: 14280046

Mon rand7

La moyenne semble correcte et les distributions de nombres semblent également correctes.

randt: avg = 3.000080 0: 14288793 1: 14280135 2: 14287848 3: 14285277 4: 14286341 5: 14278663 6: 14292943


Vous devriez probablement regarder la corrélation séquentielle. Je pense que si vous prenez des paires successives (chaque nombre "aléatoire" associé à son prédécesseur), vous pourriez trouver des choses surprenantes. Vous n'avez pas expliqué POURQUOI il devrait conserver l'uniformité de la distribution, en tout cas. Un programme de travail devrait normalement commencer par expliquer pourquoi il fonctionne.
Ian

La corrélation séquentielle s'appliquerait-elle à bon nombre de ces solutions?
philcolbourn

La corrélation séquentielle s'appliquerait-elle à bon nombre de ces solutions? Cela fait un moment que je n'ai pas essayé et je pensais l'avoir expliqué. En regardant maintenant, il semble que j'accumule des bits aléatoires dans un pool de rand5, en veillant à ce que suffisamment aient été accumulés avant de retirer suffisamment pour créer un nombre rand7 et en veillant à ne pas déborder mon accumulateur.
philcolbourn

4

Il existe des algorithmes élégants cités ci-dessus, mais voici une façon de l'aborder, bien qu'il puisse s'agir d'un rond-point. Je suppose des valeurs générées à partir de 0.

R2 = générateur de nombres aléatoires donnant des valeurs inférieures à 2 (espace d'échantillonnage = {0, 1})
R8 = générateur de nombres aléatoires donnant des valeurs inférieures à 8 (espace d'échantillonnage = {0, 1, 2, 3, 4, 5, 6, 7 })

Afin de générer R8 à partir de R2, vous exécuterez R2 trois fois et utiliserez le résultat combiné des 3 exécutions comme un nombre binaire à 3 chiffres. Voici la plage de valeurs lorsque R2 est exécuté trois fois:

0 0 0 -> 0
.
.
1 1 1 -> 7

Maintenant, pour générer R7 à partir de R8, nous exécutons simplement R7 à nouveau s'il renvoie 7:

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

La solution du rond-point consiste à générer R2 à partir de R5 (tout comme nous avons généré R7 à partir de R8), puis R8 à partir de R2, puis R7 à partir de R8.


comme un certain nombre d'autres, cette approche pourrait prendre un temps arbitrairement long par appel R7, car vous pourriez obtenir une longue chaîne de sept de R8.
Alex North-Keys

4

Voici une solution qui s'intègre entièrement dans les entiers et se situe à environ 4% de l'optimal (c'est-à-dire utilise 1,26 nombres aléatoires dans {0..4} pour chacun dans {0..6}). Le code est en Scala, mais les mathématiques doivent être raisonnablement claires dans n'importe quelle langue: vous profitez du fait que 7 ^ 9 + 7 ^ 8 est très proche de 5 ^ 11. Donc, vous choisissez un nombre à 11 chiffres dans la base 5, puis vous l'interprétez comme un nombre à 9 chiffres dans la base 7 s'il est dans la plage (en donnant 9 nombres à la base 7), ou comme un nombre à 8 chiffres s'il est supérieur au nombre à 9 chiffres, etc. .:

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

Si vous collez un test dans l'interpréteur (REPL en fait), vous obtenez:

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

La distribution est agréable et plate (dans environ 10k de 1/7 de 10 ^ 8 dans chaque bac, comme prévu d'une distribution approximativement gaussienne).


3

En utilisant un total mobile , vous pouvez à la fois

  • maintenir une distribution égale; et
  • pas à sacrifier aucun élément dans la séquence aléatoire.

Ces deux problèmes sont un problème avec les rand(5)+rand(5)...solutions de type simpliste . Le code Python suivant montre comment l'implémenter (la plupart de cela prouve la distribution).

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

Et cette sortie montre les résultats:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

Un simpliste rand(5)+rand(5), ignorant les cas où cela renvoie plus de 6 a une variation typique de 18%, 100 fois celle de la méthode indiquée ci-dessus:

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

Et, sur les conseils de Nixuz, j'ai nettoyé le script pour que vous puissiez simplement extraire et utiliser les rand7...choses:

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)

2
Euh, permettez-moi de reformuler cela. Étant donné qu'un x particulier a été produit à un moment donné de la séquence, seuls 5 des 7 numéros peuvent être produits pour le numéro suivant de la séquence. Un vrai RNG aurait tous les échantillons indépendants les uns des autres, mais dans ce cas, ils ne le sont clairement pas.
Adam Rosenfield

3
Il est vrai que la question d'origine ne spécifie pas si les fonctions d'entrée et de sortie produisent des échantillons indépendants et distribués de manière identique (iid), mais je pense qu'il est raisonnable de penser que si l'entrée rand5 () est iid, alors la sortie rand7 () devrait également être iid. Si vous ne pensez pas que ce soit raisonnable, amusez-vous en utilisant votre RNG non iid.
Adam Rosenfield

1
Alors, quel est le mot des mathématiciens de l'université?
Adam Rosenfield,

1
Cette solution est clairement cassée. Il est évident que vous devez appeler rand5 (en moyenne) plus d'une fois par appel vers rand7 et cette solution ne le fait pas. Par conséquent, les résultats ne peuvent pas être aléatoires par une définition saine de aléatoire.
Chris Suter

1
@Pax À chaque itération de votre fonction, elle ne peut renvoyer qu'une seule des cinq valeurs différentes (bien que comprises entre 0 et 6). La toute première itération ne peut renvoyer qu'un nombre compris entre 0 et 4. Ainsi, il devrait être clair que même si votre fonction peut avoir une distribution uniforme, les échantillons ne sont pas indépendants, c'est-à-dire qu'ils sont corrélés, ce qui n'est pas quelque chose que vous voulez dans un générateur de nombres aléatoires.
Chris Suter

3

Cette réponse est plus une expérience pour obtenir le plus d'entropie possible à partir de la fonction Rand5. C'est donc quelque peu flou et presque certainement beaucoup plus lent que les autres implémentations.

En supposant la distribution uniforme de 0-4 et la distribution uniforme résultante de 0-6:

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

Le nombre de bits ajoutés au tampon par appel à Rand5 est actuellement de 4/5 * 2 donc 1,6. Si la valeur de probabilité 1/5 est incluse, elle augmente de 0,05, donc 1,65, mais voyez le commentaire dans le code où j'ai dû désactiver cela.

Bits consommés par appel à Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
C'est 3 + 3/8 + 3/64 + 3/512 ... donc environ 3,42

En extrayant des informations des sept, je récupère 1/8 * 1/7 bits par appel soit environ 0,018

Cela donne une consommation nette de 3,4 bits par appel, ce qui signifie que le rapport est de 2,125 appels vers Rand5 pour chaque Rand7. L'optimum devrait être de 2,1.

J'imagine que cette approche est beaucoup plus lente que la plupart des autres ici, à moins que le coût de l'appel à Rand5 ne soit extrêmement cher (par exemple, appeler une source externe d'entropie).


Votre solution semble correcte, à part quelques erreurs simples: "if (count> 1)" devrait être "if (count <= 1)", et le "i ++" qui se produit peu de temps après devrait être à l'intérieur des accolades qui le précèdent. Je ne sais pas si BitsSet () est correct ou non, mais ce n'est pas pertinent.
Adam Rosenfield,

Dans l'ensemble, cependant, votre fonction est très difficile à comprendre. Il ne fait un peu meilleure utilisation de l' entropie qu'il serait autrement impossible, au prix de plus de complications. Il n'y a également aucune raison de remplir initialement le tampon avec 35 bits aléatoires lors du premier appel, alors que 3 suffiraient.
Adam Rosenfield,

J'ai corrigé le <= merci, l'i ++ devrait vraiment être là cependant. Cela devrait se produire sur le zéro et le cas 1 (en ajoutant respectivement un 1 ou un zéro au tampon). Ce n'est absolument pas ce que je suggérerais d'utiliser, c'est horriblement compliqué. Je voulais juste savoir à quel point je pouvais me rapprocher des limites théoriques d'entropie inhérentes au problème ... Merci pour les commentaires. Ironiquement, le remplissage du tampon lors du premier appel a été de le rendre plus simple à écrire :)
ShuggyCoUk

J'ai retravaillé cela pour être plus facile à comprendre (au détriment de la vitesse) mais aussi le rendre correct. Il n'est pas encore optimal, pour une raison quelconque, les bits 1/5 provoquent des problèmes même s'ils sont uniformes.
ShuggyCoUk

3

en php

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

boucles pour produire un nombre aléatoire entre 16 et 127, divise par seize pour créer un flottant entre 1 et 7,9375, puis arrondit pour obtenir un entier entre 1 et 7. si je ne me trompe pas, il y a 16/112 chance d'obtenir l'un des 7 résultats.


bien qu'il existe probablement une réponse plus facile similaire à celle-ci en utilisant aucune boucle conditionnelle et modulo au lieu de plancher. Je ne peux tout simplement pas calculer les chiffres pour le moment.
dqhendricks

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

problème: cela renvoie de manière non uniforme dans la plage 0-7, pas 0-6. En effet, vous pouvez avoir 7 = 111bavecp(7) = 8 / 125
bernard paulus le

3

Je pense avoir quatre réponses, deux donnant des solutions exactes comme celle de @Adam Rosenfield mais sans le problème de boucle infinie, et deux autres avec une solution presque parfaite mais une mise en œuvre plus rapide que la première.

La meilleure solution exacte nécessite 7 appels à rand5, mais permet de continuer pour comprendre.

Méthode 1 - exacte

La force de la réponse d'Adam est qu'elle donne une distribution uniforme parfaite, et il y a une très forte probabilité (21/25) que seulement deux appels à rand5 () soient nécessaires. Cependant, le pire des cas est la boucle infinie.

La première solution ci-dessous donne également une distribution uniforme parfaite, mais nécessite un total de 42 appels vers rand5. Pas de boucles infinies.

Voici une implémentation R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

Pour les personnes qui ne connaissent pas R, voici une version simplifiée:

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

La distribution de rand5sera préservée. Si nous faisons le calcul, chacune des 7 itérations de la boucle a 5 ^ 6 combinaisons possibles, donc le nombre total de combinaisons possibles est (7 * 5^6) %% 7 = 0. Ainsi, nous pouvons diviser les nombres aléatoires générés en groupes égaux de 7. Voir la méthode deux pour plus de discussion à ce sujet.

Voici toutes les combinaisons possibles:

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

Je pense qu'il est simple de montrer que la méthode d'Adam fonctionnera beaucoup plus rapidement. La probabilité qu'il y ait 42 appels ou plus rand5dans la solution d'Adam est très faible ( (4/25)^21 ~ 10^(-17)).

Méthode 2 - pas exacte

Maintenant, la deuxième méthode, qui est presque uniforme, mais nécessite 6 appels à rand5:

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Voici une version simplifiée:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

Il s'agit essentiellement d'une itération de la méthode 1. Si nous générons toutes les combinaisons possibles, voici les comptes résultants:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

Un numéro apparaîtra à nouveau dans les 5^6 = 15625essais.

Maintenant, dans la méthode 1, en ajoutant 1 à 6, nous déplaçons le nombre 2233 à chacun des points successifs. Ainsi, le nombre total de combinaisons correspondra. Cela fonctionne parce que 5 ^ 6 %% 7 = 1, puis nous faisons 7 variations appropriées, donc (7 * 5 ^ 6 %% 7 = 0).

Méthode 3 - exacte

Si l'argument des méthodes 1 et 2 est compris, la méthode 3 suit et ne nécessite que 7 appels à rand5. À ce stade, je pense que c'est le nombre minimum d'appels nécessaires pour une solution exacte.

Voici une implémentation R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

Pour les personnes qui ne connaissent pas R, voici une version simplifiée:

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

La distribution de rand5sera préservée. Si nous faisons le calcul, chacune des 7 itérations de la boucle a 5 résultats possibles, donc le nombre total de combinaisons possibles est (7 * 5) %% 7 = 0. Ainsi, nous pouvons diviser les nombres aléatoires générés en groupes égaux de 7. Voir méthode un et deux pour plus de discussion à ce sujet.

Voici toutes les combinaisons possibles:

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

Je pense qu'il est simple de montrer que la méthode d'Adam fonctionnera toujours plus rapidement. La probabilité qu'il y ait 7 appels ou plus rand5dans la solution d'Adam est encore faible ( (4/25)^3 ~ 0.004).

Méthode 4 - pas exacte

Il s'agit d'une variante mineure de la deuxième méthode. Il est presque uniforme, mais nécessite 7 appels à rand5, c'est un complément à la méthode 2:

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Voici une version simplifiée:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

Si nous générons toutes les combinaisons possibles, voici les comptes résultants:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

Deux numéros apparaîtront une fois de moins dans les 5^7 = 78125essais. Pour la plupart des buts, je peux vivre avec ça.


1
Je ne connais pas R, mais à moins que je ne comprenne pas comment cela fonctionne, la méthode 1 n'est pas exacte. Il a (5 ^ 6) ^ 7 = 5 ^ 42 résultats possibles, pas (5 ^ 6) * 7; 5 ^ 42 n'est pas divisible par 7. De même, la méthode 3 n'est pas exacte. Il a 5 ^ 7 résultats possibles, pas 5 * 7. (La dernière itération de boucle dans la méthode 3 i=7n'a également aucun effet, car l'ajout 7*rand5()à rne modifie pas la valeur du rmod 7.)
Adam Rosenfield

2

La fonction dont vous avez besoin est rand1_7 () , j'ai écrit rand1_5 () pour que vous puissiez la tester et la tracer.

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.