web-dev-qa-db-fra.com

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

Pour une fonction produisant un entier aléatoire compris entre 1 et 5, écrivez une fonction produisant un entier aléatoire compris entre 1 et 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 pour fonctionner sur un processeur plus lent?
673
praveen

Ceci 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 coller sur un jeu de fléchettes et le lancer au hasard. Si vous atteignez une valeur différente de zéro, il s'agit d'une valeur statistique aléatoire comprise entre 1 et 7, car vous avez le choix entre un nombre égal de valeurs non nulles. Si vous atteignez un zéro, continuez à lancer le Dart jusqu'à ce que vous atteigniez un non-zéro. C'est ce que fait ce code: les index i et j sélectionnent au hasard un emplacement sur le tableau 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 indéfiniment dans le pire des cas, mais statistiquement, le pire des cas ne se produit jamais. :)

554
Rob McAfee

Il n’existe pas de solution (tout à fait correcte) qui fonctionne dans un laps de temps constant, car 1/7 est un nombre décimal infini en base 5. Une solution simple consisterait à utiliser un échantillonnage par 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 une durée de fonctionnement attendue de 25/21 = 1,19 itérations de la boucle, mais il existe une probabilité infiniment petite de boucler pour toujours.

339
Adam Rosenfield

J'aimerais ajouter une autre réponse, en plus de ma première réponse . Cette réponse tente de minimiser le nombre d'appels à Rand5() par appel à Rand7(), afin de maximiser l'utilisation du caractère aléatoire. En d’autres termes, si vous considérez le hasard comme une ressource précieuse, nous voulons en utiliser le plus possible, sans jeter aucun élément aléatoire. Cette réponse présente également certaines similitudes avec la logique présentée dans réponse d'Ivan .

Le 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 log2 N. Ainsi, Rand5() a environ 2,32193 bits d'entropie et Rand7() environ 2,80735 bits d'entropie. Si nous espérons maximiser notre utilisation du hasard, nous devons utiliser tous les 2.32193 bits d'entropie de chaque appel à Rand5() et les appliquer à la génération de 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 les nombres compris dans la plage [0, 4] et Rand7() sera supposé renvoyer les nombres compris dans la plage [0, 6]. Ajuster les plages sur [1, 5] et [1, 7] est une opération triviale.

Alors comment le fait-on? Nous générons un nombre réel aléatoire infiniment précis compris entre 0 et 1 (en supposant pour le moment que nous pourrions réellement calculer et stocker un nombre aussi infiniment précis - nous je vais régler ça 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 .a1a2a3..., où chaque chiffre ai est choisi par un appel à Rand5(). Par exemple, si notre RNG choisit uni = 1 pour tout i, en ignorant alors que ce n'est pas très aléatoire, cela correspondrait au nombre réel 1/5 + 1/52 + 1/53 + ... = 1/4 (somme d'une série géométrique).

Ok, nous avons donc choisi un nombre réel aléatoire compris entre 0 et 1. J'affirme maintenant qu'un tel nombre aléatoire est uniformément distribué. Intuitivement, cela est facile à comprendre, car chaque chiffre a été choisi de manière uniforme et le nombre est infiniment précis. Cependant, une preuve formelle de cela est un peu plus compliquée, puisque nous traitons maintenant avec 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 égal à la longueur de cet intervalle, b - a. La preuve est laissée comme un exercice pour le 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 le résultat de Rand7(). Comment faisons-nous cela? Tout le contraire de ce que nous venons de faire - nous le convertissons en une décimale infinie de précision en base 7, puis chaque chiffre en base 7 correspond à une sortie de Rand7().

En prenant l'exemple de tout à l'heure, si notre Rand5() produit un flot infini de 1, 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.

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

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

  1. Multiplier par 7
  2. La partie intégrante du résultat est la base suivante à 7 chiffres
  3. Soustrayez la partie intégrale, ne laissant que la partie fractionnaire
  4. Aller à l'étape 1

Pour traiter le problème de la précision infinie, nous calculons un résultat partiel et nous stockons également une limite supérieure sur ce que le résultat pourrait être. En d’autres termes, supposons que nous ayons appelé Rand5() deux fois et qu’il renvoie 1 les deux fois. Le nombre que nous avons généré jusqu'ici est 0,11 (base 5). Quel que soit le reste de la série infinie d'appels vers Rand5(), 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 que cela pourrait prendre, nous convertissons les deux nombres en base 7. S'ils sont d'accord, le premier k chiffres, nous pouvons alors émettre en toute sécurité les prochains k chiffres - quel que soit le flux infini de chiffres de base 5, ils n’affecteront jamais les prochains k chiffres du représentation base 7!

Et c’est l’algorithme - pour générer la sortie suivante de Rand7(), nous ne générons autant de chiffres de Rand5() que nous devons nous assurer de connaître avec certitude la valeur du chiffre suivant dans la conversion de le nombre réel aléatoire à la 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 __== '__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 son état interne implique 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 en mesure la distribution. Seul le nombre entier est utilisé, les résultats sont donc exactement corrects.

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

Dans une exécution de celle-ci, j’ai fait 12091 appels à Rand5() pour 10000 appels à Rand7(), atteignant le minimum d’appels log (7)/log (5) en moyenne à 4 chiffres significatifs, et le résultat obtenu. la sortie était uniforme.

Pour porter ce code dans un langage ne contenant pas de nombres entiers arbitrairement grands, vous devez limiter les valeurs de pow5 et pow7 à la valeur maximale de votre type intégral natif. - S'ils deviennent trop gros, réinitialisez tout et recommencez. Cela augmentera le nombre moyen d'appels à Rand5() par appel à Rand7() très légèrement, mais espérons que cela ne devrait pas augmenter trop, même pour les entiers 32 ou 64 bits.

153
Adam Rosenfield

(J'ai volé la réponse d'Adam Rosenfeld et l'ai accéléré d'environ 7%.)

Supposons que Rand5 () retourne l'un des éléments {0,1,2,3,4} avec une distribution égale et que le but est return {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 trace de la plus grande valeur que la boucle puisse générer dans la variable max. Si le résultat est jusqu'ici compris entre max% 7 et max-1, le résultat sera uniformément distribué dans cette plage. Sinon, nous utilisons le reste, qui est aléatoire entre 0 et max% 7-1, et un autre appel à Rand () pour créer un nouveau numéro et un nouveau max. Puis on recommence.

Edit: Attendez-vous à 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()
36
Eyal

Algorithme:

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

Utilisez Rand (5) pour remplir chaque bit de façon aléatoire 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, alors ignorer et recommencer (rejet) 

De cette façon, nous pouvons remplir 3 bits de manière aléatoire avec 0/1 et obtenir ainsi un nombre compris entre 1 et 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;
        }
    }
}
27
Anand
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 );
    }
}
19
Mike F
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = Rand5()) == 3){};
          ans += (r < 3) >> i
     }
}
15
Nescio
Rand7() = (Rand5()+Rand5()+Rand5()+Rand5()+Rand5()+Rand5()+Rand5())%7+1

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

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 output=x peut se produire étant donné l'appel n à Rand5.

  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]
15
BCS

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 en désordre, 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;
}    
13
jason

Si nous considérons la contrainte supplémentaire consistant à essayer de donner la réponse la plus efficace, c’est-à-dire que, étant donné un flux en entrée, I, d’entiers uniformément répartis de longueur m de 1-5, un flux O, d’entiers uniformément répartis de 1-7 la plus grande longueur par rapport à m, dites L(m)

La méthode la plus simple pour analyser cela consiste à traiter les flux I et O comme des nombres à 5 et 7 nombres 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 la même manière 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>0 et aussi petite que possible. Ensuite, il existe une mappe uniforme du flux d'entrée de longueur m aux entiers allant de 1 à 5^m et une autre mappe uniforme allant des entiers de 1 à 7^n au flux de sortie de longueur n où nous pourrions être amenés à perdre quelques cas du flux d'entrée lorsque l'entier mappé dépasse 7^n.

Cela donne donc une valeur pour L(m) d’environ m (log5/log7) qui correspond approximativement à .82m.

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

La question est de savoir comment atteindre la meilleure valeur possible de m (log5/log7). Par exemple, lorsque ce nombre s'approche d'un nombre entier proche, pouvons-nous trouver un moyen d'atteindre ce nombre entier exact de valeurs de sortie? 

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

Si nous admettons que T7(X) soit la longueur moyenne de la séquence de sortie de random(1-7) entiers dérivée d’une entrée uniforme de taille X, en supposant que 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 une probabilité de 7 ^ n0/5 ^ m avec un résidu de longueur 5^m-7^n0 avec une probabilité (5^m-7^n0)/5^m).

Si nous continuons à nous substituer, 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 des cas possibles est mon cas original 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 auparavant.

Le pire des cas est lorsque nous ne pouvons trouver que k et s.t 5 ^ m = kx7 + s.

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

D'autres cas sont quelque part entre les deux. Il serait intéressant de voir à quel point nous pouvons faire pour les très grands m, c’est-à-dire comment bien obtenir le terme d'erreur:

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

Il semble impossible d'obtenir e(m) = o(1) en général, mais nous espérons pouvoir prouver e(m)=o(m).

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

Je suis sûr qu'il y a beaucoup de théorie sur ce sujet que je pourrais jeter un coup d'oeil et rendre compte à un moment donné. 

12
Ivan

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

Cette fonction effectue des calculs bruts "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;
}
11
Will Hartung

Pourquoi ne pas le faire simple?

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

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

10
Ante

Voici une implémentation Python 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 jeter des algorithmes que je suis en train de regarder dans Python pour pouvoir les utiliser, je pensais les poster ici, dans l'espoir que cela serait utile à quelqu'un, pas que cela ait pris longtemps à être juxtaposés.

10
James McMahon

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

  • x = 5 ^ n (dans son cas: n = 2)
  • manipuler n appels Rand5 pour obtenir un numéro y dans la plage [1, x]
  • z = ((int) (x/7)) * 7
  • si y> z, essayez à nouveau. 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’une seule élimination: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Vous appelez Rand5 plusieurs fois. Cependant, vous avez beaucoup moins de chances d’obtenir une valeur jetable (ou une boucle infinie). S'il existe un moyen de ne pas avoir de valeur de rejet possible pour y, je ne l'ai pas encore trouvée.

8
Dinah

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 d'autres, mais je crois que cela minimise les appels à Rand5. Comme avec d'autres solutions, il y a une faible probabilité que la boucle soit bouclée pendant longtemps.

8
Chris Suter

En supposant que Rand (n) ici signifie "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 seulement randint (5) et des constantes pour produire l’effet de randint (7) . Un peu bête, 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
8
Joshua Fox
int Rand7() {
    int value = Rand5()
              + Rand5() * 2
              + Rand5() * 3
              + Rand5() * 4
              + Rand5() * 5
              + Rand5() * 6;
    return value%7;
}

Contrairement à la solution choisie, l'algorithme fonctionnera en temps constant. Il fait cependant 2 appels de plus à 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 chance en plus que tout autre nombre), mais dans la plupart des cas, la garantie d'un temps constant l'emporte probablement sur cette imprécision.

Explication

Cette solution découle du fait que le nombre 15.624 est divisible par 7 et que, par conséquent, 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 faisant rouler 6 fois le Rand5 et en les utilisant pour former les chiffres d’un nombre en 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

Alors

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 15.624 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, alors

a^(p-1) = 1 mod p

Donc cela nous donne,

(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 en forme de base 5 et nous pouvons donc voir que cette méthode peut être utilisée pour passer d’un générateur de nombres aléatoires à un 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.

7
Thirlan

Simple et efficace:

int Rand7 ( void )
{
    return 4; // this number has been calculated using
              // Rand5() and is in the range 1..7
}

(Inspiré par Quel est votre dessin préféré de "programmeur"? ).

7
chiccodoro

Dans la mesure où il ne reste plus que sept possibilités parmi lesquelles choisir, tracez un autre nombre aléatoire, qui multiplie par cinq le nombre de possibilités. 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;
}
6
bmcnett

Je n'aime pas les gammes à partir de 1, alors 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;
}
6
fredoverflow

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

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

Besoin de planter les graines à l'avance.

5
Kugel

Je sais que la réponse a été donnée, mais cela semble fonctionner correctement, mais je ne peux pas vous dire s'il y a un parti pris. Mon «test» suggère que c'est, au moins, raisonnable.

Peut-être Adam Rosenfield aurait-il la gentillesse de commenter?

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

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

Pour éviter que l’accumulateur ne déborde, et comme l’accumulateur est au mod 7, 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 'Real' Rand mod 5 ou 7

Rand5: moy = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539

Mon rand7

La moyenne semble correcte et les distributions de nombres aussi.

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

5
philcolbourn

Voici une solution qui s’intègre parfaitement dans les entiers et se situe à environ 4% de la valeur optimale (c’est-à-dire utilise 1,26 nombres aléatoires dans {0..4} pour chaque unité dans {0..6}). Le code est en scala, mais les calculs doivent être raisonnablement clairs dans toutes les langues: vous profitez du fait que 7 ^ 9 + 7 ^ 8 est très proche de 5 ^ 11. Donc, vous choisissez un numéro à 11 chiffres en base 5, puis vous l'interprétez comme un numéro à 9 chiffres en base 7 s'il est dans la plage (donnant 9 chiffres en 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ète (en fait, le REPL), 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 (environ 10k de 1/7 sur 10 ^ 8 dans chaque casier, comme prévu d’une distribution approximativement gaussienne).

4
Rex Kerr

Il existe des algorithmes élégants cités ci-dessus, mais voici une façon de l’aborder, bien que cela puisse être détourné. 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 échantillon = {0, 1})
R8 = générateur de nombres aléatoires donnant des valeurs inférieures à 8 (espace échantillon = {0, 1, 2, 3, 4, 5, 6, 7})

Pour générer R8 à partir de R2, vous exécuterez R2 deux fois et utiliserez le résultat combiné des 3 exécutions sous forme de 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 lançons simplement R7 s'il renvoie 7:

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

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

4
Ashwin

En utilisant un total glissant , vous pouvez à la fois

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

Ces deux problèmes sont un problème avec les solutions simplistes de type Rand(5)+Rand(5)...-. Le code Python suivant montre comment l'implémenter (l'essentiel est de prouver 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%)

Une Rand(5)+Rand(5) simpliste__, ignorant les cas où cela renvoie plus de 6, présente 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 afin que vous puissiez simplement extraire et utiliser les éléments Rand7...:

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)
3
paxdiablo

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 compris entre 16 et 127, se divise par seize pour créer un flottant compris entre 1 et 7,9375, puis arrondit vers le bas pour obtenir un entier compris entre 1 et 7. Si je ne me trompe pas, il y a 16/112 chances d'obtenir l'un des 7 résultats.

3
dqhendricks

Cette réponse est davantage une expérience visant à obtenir le plus d'entropie possible à partir de la fonction Rand5. Il est donc quelque peu flou et certainement plus lent que d’autres implémentations.

En supposant que 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 à la mémoire tampon par appel de Rand5 est actuellement de 4/5 * 2, donc 1,6 . Si la probabilité sur 1/5 est incluse, elle augmente de 0,05 alors 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 * (...
Ceci est 3 + 3/8 + 3/64 + 3/512 ... donc environ 3,42

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

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

J'imagine que cette approche est significativement plus lente que la plupart des autres, à moins que le coût de l'appel vers Rand5 soit extrêmement coûteux (appelant une source d'entropie externe).

3
ShuggyCoUk
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
3
maxchengcn
Rand25() =5*(Rand5()-1) + Rand5()

Rand7() { 
   while(true) {
       int r = Rand25();
       if (r < 21) return r%3;         
   }
}

Pourquoi cela fonctionne-t-il? La probabilité que la boucle s'exécute pour toujours est égale à 0.

2
ekr

La fonction dont vous avez besoin est Rand1_7 () , j’ai écrit Rand1_5 () pour que vous puissiez la tester et la représenter.

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
2
Andrea Ambu

juste mettre à l'échelle votre sortie de votre première fonction

0) you have a number in range 1-5
1) subtract 1 to make it in range 0-4
2) multiply by (7-1)/(5-1) to make it in range 0-6
3) add 1 to increment the range: Now your result is in between 1-7
2
cartonn

Pour les valeurs 0 à 7, vous disposez des éléments suivants:

0 000
1 001
2 010
3 011
4 100
5 101
6 110
7 111

De bit à gauche de droite à gauche Rand5 () a p(1) = {2/5, 2/5, 3/5}. Donc, si nous complétons ces distributions de probabilité (~ Rand5 ()), nous devrions pouvoir l'utiliser pour produire notre nombre. Je vais essayer de faire rapport plus tard avec une solution. Quelqu'un a des pensées?

R

2
Rob Leclerc
int getOneToSeven(){
    int added = 0;
    for(int i = 1; i<=7; i++){
        added += getOneToFive();
    }
    return (added)%7+1;
}
2
pas1311

Nous utilisons la convention Rand(n) -> [0, n - 1] ici

D'après la plupart des réponses que j'ai lues, elles fournissent soit l'uniformité, soit la garantie d'arrêt, mais pas les deux (la deuxième réponse d'Adam Rosenfeld pourrait l'être).

Il est toutefois possible de le faire. Nous avons essentiellement cette distribution:

Rand5_proba.png

Cela nous laisse un trou dans la distribution par rapport à [0-6]: 5 et 6 n’ont aucune probabilité d’occurrence. Imaginons maintenant que nous essayons de combler le trou en déplaçant la distribution de probabilité et la somme.

En effet, on peut déplacer la distribution initiale avec un décalage de un, puis répéter en additionnant la distribution obtenue avec la distribution initiale décalée de deux, puis trois, et ainsi de suite, jusqu’à 7, non inclus (nous avons couvert l’ensemble Ceci est indiqué sur la figure suivante. L'ordre des couleurs, correspondant aux étapes , Est le suivant: bleu -> vert -> cyan -> blanc -> magenta -> jaune -> rouge.

fig_moving_average_proba.png

Parce que chaque créneau est couvert par 5 des 7 distributions décalées (le décalage varie de 0 à 6), et parce que nous supposons que les nombres aléatoires sont indépendants d’un appel ran5() à un autre, nous obtenons

p(x) = 5 / 35 = 1 / 7       for all x in [0, 6]

Cela signifie que, étant donné 7 nombres aléatoires indépendants de ran5(), nous pouvons Calculer un nombre aléatoire avec une probabilité uniforme dans la plage [0-6] . En fait, la distribution de probabilité ran5 () N'a même pas besoin d'être uniforme, tant que les échantillons sont indépendants (la distribution reste donc identique d’un essai à l’autre) . Cela vaut également pour les nombres autres que 5 et 7.

Cela nous donne la fonction python suivante:

def Rand_range_transform(rands):
    """
    returns a uniform random number in [0, len(rands) - 1]
    if all r in rands are independent random numbers from the same uniform distribution
    """
    return sum((x + i) for i, x in enumerate(rands)) % len(rands) # a single modulo outside the sum is enough in modulo arithmetic

Cela peut être utilisé comme ceci:

Rand5 = lambda : random.randrange(5)

def Rand7():
    return Rand_range_transform([Rand5() for _ in range(7)])

Si nous appelons Rand7() 70000 fois, nous pouvons obtenir:

max: 6 min: 0 mean: 2.99711428571 std: 2.00194697049
0:  10019
1:  10016
2:  10071
3:  10044
4:  9775
5:  10042
6:  10033

C'est bien, mais loin d'être parfait. Le fait est que l’une de nos hypothèses est Probablement fausse dans cette implémentation: nous utilisons un PRNG et, en tant que tel, le résultat Du prochain appel dépend du dernier résultat.

Cela dit, en utilisant une source de nombres vraiment aléatoire, la sortie devrait également être Vraiment aléatoire. Et cet algorithme se termine dans tous les cas.

Mais cela a un coût: nous avons besoin de 7 appels à Rand5() pour un seul Rand7()call.

2
bernard paulus

Cette solution ne gaspille aucune entropie et donne le premier nombre réellement aléatoire disponible dans la plage. À chaque itération, la probabilité de ne pas obtenir de réponse diminue de manière prouvée. La probabilité d'obtenir une réponse en N itérations est la probabilité qu'un nombre aléatoire compris entre 0 et max (5 ^ N) soit inférieur au plus grand multiple de sept de cette plage (max-max% 7). Doit itérer au moins deux fois. Mais c'est nécessairement vrai pour toutes les solutions.

int random7() {
  range = 1;
  remainder = 0;

  while (1) {
    remainder = remainder * 5 + random5() - 1;
    range = range * 5;

    limit = range - (range % 7);
    if (remainder < limit) return (remainder % 7) + 1;

    remainder = remainder % 7;
    range = range % 7;
  }
}

Numériquement équivalent à:

r5=5;
num=random5()-1;
while (1) {
   num=num*5+random5()-1;
   r5=r5*5;
   r7=r5-r5%7;
   if (num<r7) return num%7+1;
}

Le premier code le calcule sous forme modulo. Le deuxième code est tout simplement mathématique. Ou j'ai fait une erreur quelque part. :-)

2
Martin
package CareerCup;

public class RangeTransform {
 static int counter = (int)(Math.random() * 5 + 1);

 private int func() {
  return (int) (Math.random() * 5 + 1);
 }

 private int getMultiplier() {
  return counter % 5 + 1;
 }

 public int rangeTransform() {
  counter++;
  int count = getMultiplier();
  int mult = func() + 5 * count;
  System.out.println("Mult is : " + 5 * count);
  return (mult) % 7 + 1;
 }

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  RangeTransform rangeTransform = new RangeTransform();
  for (int i = 0; i < 35; i++)
   System.out.println("Val is : " + rangeTransform.rangeTransform());
 }
}
2
DeWan

En supposant que Rand donne une pondération égale à tous les bits, puis masque avec la limite supérieure.

int i = Rand(5) ^ (Rand(5) & 2);

Rand(5) ne peut que renvoyer: 1b, 10b, 11b, 100b, 101b. Il vous suffit de vous préoccuper parfois de régler le bit 2.

2
Ben
function Rand7
   put 200 into x
   repeat while x > 118
      put ((random(5)-1) * 25) + ((random(5)-1) * 5) + (random(5)-1) into x
   end repeat
   return (x mod 7) + 1
end Rand7

Trois appels à Rand5, qui ne se répète que 6 fois sur 125, en moyenne.

Pensez-y comme un tableau 3D, 5x5x5, rempli avec 1 à 7 répétitions et 6 blancs. Re-roll sur les blancs. Les appels Rand5 créent un index de base 5 à trois chiffres dans ce tableau.

Il y aurait moins de répétitions avec un 4D, ou des tableaux à N dimensions plus élevés, mais cela signifie que davantage d'appels à la fonction Rand5 deviennent standard. Vous commencerez à obtenir des rendements en baisse d'efficacité à des dimensions plus élevées. Trois me semble être un bon compromis, mais je ne les ai pas testés les uns contre les autres pour en être sûr. Et ce serait spécifique à l'implémentation de Rand5.

2
user470894

Pourquoi ça ne marche pas? Autre que l’appel supplémentaire à Rand5 ()?

i = Rand5() + Rand5() + (Rand5() - 1) //Random number between 1 and 14

i = i % 7 + 1;
2
Sam

C'est la réponse la plus simple que j'ai pu créer après avoir examiné les réponses des autres:

def r5tor7():
    while True:
        cand = (5 * r5()) + r5()
        if cand < 27:
            return cand

cand est compris dans l'intervalle [6, 27] et les résultats possibles sont également répartis si les résultats possibles de r5 () sont également répartis. Vous pouvez tester ma réponse avec ce code:

from collections import defaultdict

def r5_outcome(n):
    if not n:
        yield []
    else:
        for i in range(1, 6):
            for j in r5_outcome(n-1):
                yield [i] + j

def test_r7():
    d = defaultdict(int)
    for x in r5_outcome(2):
        s = sum([x[i] * 5**i for i in range(len(x))])
        if s < 27:
            d[s] += 1
    print len(d), d

r5_outcome(2) génère toutes les combinaisons possibles de résultats r5 (). J'utilise le même filtre pour tester que dans mon code de solution. Vous pouvez voir que tous les résultats sont les mêmes, probablement parce qu'ils ont la même valeur.

2
hughdbrown

Voici ce que j'ai trouvé:

  1. Random5 produit une plage de 1 ~ 5, distribuée de manière aléatoire
  2. Si nous l'exécutons 3 fois et que nous les additionnons, nous aurons une plage de 3 ~ 15, répartis au hasard.
  3. Effectuer des calculs sur la plage de 3 à 15
    1. (3 ~ 15) - 1 = (2 ~ 14)
    2. (2 ~ 14)/2 = (1 ~ 7)

Nous obtenons ensuite une plage de 1 à 7, qui correspond au Random7 que nous recherchons.

2
ming_codes

Voici ma mise en œuvre générale, pour générer un uniforme dans la plage [0, N-1] pour un générateur uniforme dans la plage [0, B-1]. 

public class RandomUnif {

    public static final int BASE_NUMBER = 5;

    private static Random Rand = new Random();

    /** given generator, returns uniform integer in the range 0.. BASE_NUMBER-1
    public static int randomBASE() {
        return Rand.nextInt(BASE_NUMBER);
    }

    /** returns uniform integer in the range 0..n-1 using randomBASE() */
    public static int randomUnif(int n) {
        int Rand, factor;
        if( n <= 1 ) return 0;
        else if( n == BASE_NUMBER ) return randomBASE();
        if( n < BASE_NUMBER ) {
            factor = BASE_NUMBER / n;
            do
                Rand = randomBASE() / factor;
            while(Rand >= n);
            return Rand;
        } else {
            factor = (n - 1) / BASE_NUMBER + 1;
            do {
                Rand = factor * randomBASE() + randomUnif(factor);
            } while(Rand >= n);
            return Rand;
        }
    }
}

Pas spectaculairement efficace, mais général et compact. Nombre moyen d'appels au générateur de base:

 n  calls
 2  1.250 
 3  1.644 
 4  1.252 
 5  1.000 
 6  3.763 
 7  3.185 
 8  2.821 
 9  2.495 
10  2.250 
11  3.646 
12  3.316 
13  3.060 
14  2.853 
15  2.650 
16  2.814 
17  2.644 
18  2.502 
19  2.361 
20  2.248 
21  2.382 
22  2.277 
23  2.175 
24  2.082 
25  2.000 
26  5.472 
27  5.280 
28  5.119 
29  4.899 
1
leonbloy

La première chose qui m'est venue à l’esprit est la suivante. Mais je ne sais pas s'il est uniformément distribué. Implémenté en python

importer au hasard

def Rand5 ():

return random.randint (1,5)

def Rand7 ():

return (((Rand5 () -1) * Rand5 ())% 7) +1

1
HOT

Est venu ici d'un lien de l'expansion d'une gamme de flotteurs. Celui-ci est plus amusant. Au lieu de comment je suis arrivé à la conclusion, il m'est apparu que pour une fonction génératrice d'entiers aléatoires donnée f avec "base" b (4 dans ce cas, je vais dire pourquoi), elle peut être développée comme ci-dessous :

(b^0 * f() + b^1 * f() + b^2 * f() .... b^p * f()) / (b^(p+1) - 1) * (b-1)

Cela convertira un générateur aléatoire en un générateur FLOAT. Je définirai ici 2 paramètres, la b et la p. Bien que la "base" soit 4, b peut en réalité être n'importe quoi, il peut aussi s'agir d'un nombre irrationnel, etc. p, j'appelle précision, une mesure de la précision avec laquelle vous voulez que votre générateur de flotteurs soit. Considérez ceci comme le nombre d'appels passés à Rand5 pour chaque appel de Rand7.

Mais j’ai réalisé que si vous définissez b sur la base + 1 (ce qui correspond à 4 + 1 = 5 dans ce cas), c’est un point idéal et vous obtiendrez une distribution uniforme. Tout d’abord, débarrassez-vous de ce générateur 1-5, c’est en vérité Rand4 () + 1:

function Rand4(){
    return Math.random() * 5 | 0;
}

Pour y arriver, vous pouvez remplacer Rand4 par Rand5()-1

Ensuite, il faut convertir Rand4 d’un générateur d’entiers en un générateur de float.

function toFloat(f,b,p){
    b = b || 2;
    p = p || 3;
    return (Array.apply(null,Array(p))
    .map(function(d,i){return f()})
    .map(function(d,i){return Math.pow(b,i)*d})
    .reduce(function(ac,d,i){return ac += d;}))
    /
    (
        (Math.pow(b,p) - 1)
        /(b-1)
    )
}

Ceci appliquera la première fonction que j'ai écrite à une fonction Rand donnée. Essayez le:

toFloat(Rand4) //1.4285714285714286 base = 2, precision = 3
toFloat(Rand4,3,4) //0.75 base = 3, precision = 4
toFloat(Rand4,4,5) //3.7507331378299122 base = 4, precision = 5
toFloat(Rand4,5,6) //0.2012288786482335 base = 5, precision =6
...

Vous pouvez maintenant convertir cette plage de flottement (0-4 INCLUSIVE) en une autre plage de flottement, puis la rétrograder en tant qu'entier. Ici notre base est 4 car nous traitons avec Rand4, donc une valeur b=5 vous donnera une distribution uniforme. À mesure que le nombre b dépasse 4, vous allez commencer à introduire des intervalles périodiques dans la distribution. J'ai testé pour des valeurs b allant de 2 à 8 avec 3000 points chacune et comparé à Math.random natif de javascript, me semble encore meilleur que le natif lui-même:

http://jsfiddle.net/ibowankenobi/r57v432t/

Pour le lien ci-dessus, cliquez sur le bouton "bin" en haut des distributions pour réduire la taille du binning. Le dernier graphique est natif en Math.random, le 4ème où d = 5 est uniforme.

Après avoir obtenu votre plage de flottaison, multipliez par 7 et jetez la partie décimale ou multipliez par 7, soustrayez 0.5 et arrondissez:

((toFloat(Rand4,5,6)/4 * 7) | 0) + 1   ---> occasionally you'll get 8 with 1/4^6 probability.
Math.round((toFloat(Rand4,5,6)/4 * 7) - 0.5) + 1 --> between 1 and 7
1
ibrahim tanyalcin

J'ai pensé à une solution intéressante à ce problème et je voulais le partager.

function Rand7() {

    var returnVal = 4;

    for (var n=0; n<3; n++) {
        var Rand = Rand5();

        if (Rand==1||Rand==2){
            returnVal+=1;
        }
        else if (Rand==3||Rand==4) {
            returnVal-=1;
        }
    }

    return returnVal;
}

J'ai créé une fonction de test qui parcourt Rand7 () 10 000 fois, résume toutes les valeurs de retour et les divise par 10 000. Si Rand7 () fonctionne correctement, notre moyenne calculée doit être 4 - par exemple, (1 + 2 + 3 + 4 + 5 + 6 + 7/7) = 4. Après avoir effectué plusieurs tests, la moyenne est bien à 4 :)

1
Eric Rowell

Beaucoup de solutions ici ne produisent pas une distribution uniforme et de nombreux commentaires le soulignent, mais la la question ne le dit pas comme une exigence . La solution la plus simple est:

int Rand_7() { return Rand_5(); }

Un nombre entier aléatoire compris entre 1 et 5 est clairement compris entre 1 et 7. Bien, techniquement, la solution la plus simple consiste à renvoyer une constante, mais c'est trop trivial. 

Cependant, je pense que l’existence de la fonction Rand_5 est un fouillis rouge. Supposons que la question a été posée comme "produire un générateur de nombres pseudo-aléatoires uniformément distribué avec une sortie entière comprise entre 1 et 7". C'est un problème simple (pas techniquement simple, mais déjà résolu, vous pouvez donc le rechercher.) 

D'autre part, si la question est interprétée comme signifiant que vous avez réellement un générateur de nombres véritablement aléatoires pour les entiers compris entre 1 et 5 (et non pseudo aléatoire), la solution est la suivante:

1) examine the Rand_5 function
2) understand how it works
3) profit
1
William Pursell

que dis-tu de ça

Rand5 ()% 2 + Rand5 ()% 2 + Rand5 ()% 2 + Rand5 ()% 2 + Rand5 ()% 2 + Rand5 ()% 2

Pas sûr que ce soit uniforme distribué. Aucune suggestion?

1
rich

Une autre réponse qui semble ne pas avoir été couverte ici:

int Rand7() {
  int r = 7 / 2;
  for (int i = 0; i < 28; i++)
    r = ((Rand5() - 1) * 7 + r) / 5;
  return r + 1;
}

À chaque itération, r est une valeur aléatoire comprise entre 0 et 6 inclus. Celui-ci est ajouté (base 7) à une valeur aléatoire comprise entre 0 et 4 inclus, et le résultat est divisé par 5, ce qui donne une nouvelle valeur aléatoire comprise entre 0 et 6 inclus. r commence par un biais important (r = 3 est très biaisé!) mais chaque itération divise ce biais par 5.

Cette méthode est pas parfaitement uniforme; Cependant, le biais est extrêmement faible. Quelque chose dans l'ordre de 1/(2 ** 64). Ce qui est important dans cette approche est qu’elle a un temps d’exécution constant (en supposant que Rand5() a également un temps d’exécution constant). Aucune préoccupation théorique qu'un appel malchanceux pourrait itérer à tout jamais pour prendre de mauvaises valeurs.


En outre, une réponse sarcastique pour faire bonne mesure (délibérément ou non, il a été couvert):

1-5 est déjà compris dans la plage 1-7. Par conséquent, l'implémentation suivante est valide:

int Rand7() {
  return Rand5();
}

La question ne demandait pas une distribution uniforme.

1
sh1

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 poursuivons afin de comprendre. 

Méthode 1 - Exact

La force de la réponse d'Adam est qu'il donne une distribution uniforme parfaite et qu'il est très probable (21/25) que seulement deux appels à Rand5 () seront 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 à Rand5. Pas de boucles infinies.

Voici une implémentation de 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 ne connaissant 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 Rand5 sera préservée. Si nous faisons le calcul, chacune des 7 itérations de la boucle a 5 ^ 6 combinaisons possibles, le nombre total de combinaisons possibles est donc (7 * 5^6) %% 7 = 0. Ainsi, nous pouvons diviser les nombres aléatoires générés en groupes égaux de 7. Voir la deuxième méthode pour plus de détails à 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 sera beaucoup plus rapide. La probabilité qu'il y ait au moins 42 appels à Rand5 dans la solution d'Adam est très petite ((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
}

Ceci est essentiellement 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 une fois de plus dans les essais 5^6 = 15625.

Maintenant, dans la méthode 1, en ajoutant 1 à 6, nous déplaçons le nombre 2233 vers chacun des points successifs. Ainsi, le nombre total de combinaisons correspondra. Cela fonctionne parce que 5 ^ 6 %% 7 = 1, et ensuite 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, j'estime qu'il s'agit du nombre minimal d'appels requis pour une solution exacte.

Voici une implémentation de R:

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

Rand7 <- function()  (sum(sapply(1:7, function(i) i * Rand5())) %% 7) + 1

Pour les personnes ne connaissant 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 Rand5 sera préservée. Si nous faisons le calcul, chacune des 7 itérations de la boucle a 5 résultats possibles, le nombre total de combinaisons possibles est donc (7 * 5) %% 7 = 0. Ainsi, nous pouvons diviser les nombres aléatoires générés en groupes égaux de 7. Voir les méthodes 1 et 2 pour plus de détails à 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 sera encore plus rapide. La probabilité qu'il y ait au moins 7 appels à Rand5 dans la solution d'Adam reste faible ((4/25)^3 ~ 0.004). 

Méthode 4 - pas exacte

Ceci est une variante mineure de la deuxième méthode. Il est presque uniforme, mais nécessite 7 appels à Rand5, soit 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 nombres apparaîtront une fois moins dans les essais 5^7 = 78125. Dans la plupart des cas, je peux vivre avec ça.

1
Shambho

Cette solution a été inspirée par Rob McAfee.
Cependant, il n’a pas besoin de boucle et le résultat est une distribution uniforme:

// Returns 1-5
var rnd5 = function(){
   return parseInt(Math.random() * 5, 10) + 1;
}
// Helper
var lastEdge = 0;
// Returns 1-7
var rnd7 = function () {
  var map = [
     [ 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 ]
  ];
  var result = map[rnd5() - 1][rnd5() - 1];
  if (result > 0) {
    return result;
  }
  lastEdge++;
  if (lastEdge > 7 ) {
    lastEdge = 1;
  }
  return lastEdge;
};

// Test the a uniform distribution
results = {}; for(i=0; i < 700000;i++) { var Rand = rnd7(); results[Rand] = results[Rand] ? results[Rand] + 1 : 1;} 
console.log(results)

Résultat: [1: 99560, 2: 99932, 3: 100355, 4: 100262, 5: 99603, 6: 100062, 7: 100226]

jsFiddle

1
jantimon
function Rand7() {
    while (true) { //lowest base 5 random number > 7 reduces memory
        int num = (Rand5()-1)*5 + Rand5()-1;
    if (num < 21)  // improves performance
        return 1 + num%7;
    }
}

Code python:

from random import randint
def Rand7():
    while(True):
        num = (randint(1, 5)-1)*5 + randint(1, 5)-1
        if num < 21:
                return 1 + num%7

Test de distribution pour 100 000 exécutions:

>>> rnums = []
>>> for _ in range(100000):
    rnums.append(Rand7())
>>> {n:rnums.count(n) for n in set(rnums)}
{1: 15648, 2: 15741, 3: 15681, 4: 15847, 5: 15642, 6: 15806, 7: 15635}
1
dansalmo

Étant donné une fonction produisant un entier aléatoire compris entre 1 et 5 Rand5(), écrivez une fonction produisant un entier aléatoire compris entre 1 et 7 Rand7()

Dans la solution proposée, je n’appelle que Rand5 une seule fois.

Real Solution

float Rand7()
{
    return (Rand5() * 7.0) / 5.0 ;
}

La distribution ici est mise à l'échelle, donc cela dépend directement de la distribution de Rand5

Solution entière

int Rand7()
{
    static int prev = 1;

    int cur = Rand5();

    int r = cur * prev; // 1-25

    float f = r / 4.0; // 0.25-6.25

    f = f - 0.25; // 0-6

    f = f + 1.0; // 1-7

    prev = cur;

    return (int)f;
}

La distribution dépend ici de la série Rand7(i) ~ Rand5(i) * Rand5(i-1)

avec Rand7(0) ~ Rand5(0) * 1

1
Khaled.K

Voici une réponse tirant parti des fonctionnalités de C++ 11

#include <functional>
#include <iostream>
#include <ostream>
#include <random>

int main()
{
    std::random_device rd;
    unsigned long seed = rd();
    std::cout << "seed = " << seed << std::endl;

    std::mt19937 engine(seed);

    std::uniform_int_distribution<> dist(1, 5);
    auto Rand5 = std::bind(dist, engine);

    const int n = 20;
    for (int i = 0; i != n; ++i)
    {
        std::cout << Rand5() << " ";
    }
    std::cout << std::endl;

    // Use a lambda expression to define Rand7
    auto Rand7 = [&Rand5]()->int
    {
        for (int result = 0; ; result = 0)
        {
            // Take advantage of the fact that
            // 5**6 = 15625 = 15624 + 1 = 7 * (2232) + 1.
            // So we only have to discard one out of every 15625 numbers generated.

            // Generate a 6-digit number in base 5
            for (int i = 0; i != 6; ++i)
            {
                result = 5 * result + (Rand5() - 1);
            }

            // result is in the range [0, 15625)
            if (result == 15625 - 1)
            {
                // Discard this number
                continue;
            }

            // We now know that result is in the range [0, 15624), a range that can
            // be divided evenly into 7 buckets guaranteeing uniformity
            result /= 2232;
            return 1 + result;
        }
    };

    for (int i = 0; i != n; ++i)
    {
        std::cout << Rand7() << " ";
    }
    std::cout << std::endl;

    return 0;
}
1
user515430
int Rand7()
{
    return ( Rand5() + (Rand5()%3) );
}
  1. Rand5 () - Retourne les valeurs de 1-5
  2. Rand5 ()% 3 - Retourne les valeurs de 0-2
  3. Donc, en sommant, la valeur totale sera comprise entre 1 et 7
1
prabhakcv

Ceci est similaire à @RobMcAfee sauf que j'utilise un nombre magique au lieu d'un tableau à 2 dimensions.

int Rand7() {
    int m = 1203068;
    int r = (m >> (Rand5() - 1) * 5 + Rand5() - 1) & 7;

    return (r > 0) ? r : Rand7();
}
1
invisal

Je pense que vous réfléchissez trop. Cette solution simple ne fonctionne-t-elle pas?

int Rand7(void)
{
    static int startpos = 0;
    startpos = (startpos+5) % (5*7);
    return (((startpos + Rand5()-1)%7)+1);
}
1
Graham Toal
  1. Qu'est-ce qu'une solution simple? (Rand5() + Rand5()) % 7 + 1
  2. Quelle est une solution efficace pour réduire l'utilisation de la mémoire ou pour fonctionner sur un processeur plus lent? Yes, this is effective as it calls Rand5() only twice and have O(1) space complexity

Considérez Rand5() donne des nombres aléatoires de 1 à 5 (inclus).
(1 + 1)% 7 + 1 = 3 
(1 + 2)% 7 + 1 = 4 
(1 + 3)% 7 + 1 = 5 
(1 + 4)% 7 + 1 = 6 
(1 + 5)% 7 + 1 = 7 

(2 + 1)% 7 + 1 = 4 
(2 + 2)% 7 + 1 = 5 
.__ (2 + 3)% 7 + 1 = 6 
.__ (2 + 4)% 7 + 1 = 7 
.__ (2 + 5)% 7 + 1 = 1 
...

(5 + 1)% 7 + 1 = 7 
(5 + 2)% 7 + 1 = 1 
(5 + 3)% 7 + 1 = 2 
(5 + 4)% 7 + 1 = 3 
.__ (5 + 5)% 7 + 1 = 4 
...

etc

0
Devendra Lattu

Ce serait cool si quelqu'un pouvait me donner des retours sur celui-ci, j'ai utilisé JUNIT sans affirmer Pattern car il est facile et rapide de le faire fonctionner sous Eclipse, j'aurais aussi pu définir une méthode principale. En passant, je suppose que Rand5 donne les valeurs 0-4, ajouter 1 lui donnerait 1-5, comme avec Rand7 ... La discussion devrait donc porter sur la solution, sa distribution, et non pas sur le passage de 0 à 4 ou 1-5 ...

package random;

import Java.util.Random;

import org.junit.Test;

public class RandomTest {


    @Test
    public void testName() throws Exception {
        long times = 100000000;
        int indexes[] = new int[7];
        for(int i = 0; i < times; i++) {
            int Rand7 = Rand7();
            indexes[Rand7]++;
        }

        for(int i = 0; i < 7; i++)
            System.out.println("Value " + i + ": " + indexes[i]);
    }


    public int Rand7() {
        return (Rand5() + Rand5() + Rand5() + Rand5() + Rand5() + Rand5() + Rand5()) % 7;
    }


    public int Rand5() {
        return new Random().nextInt(5);
    }


}

Quand je le lance, j'obtiens ce résultat:

Value 0: 14308087
Value 1: 14298303
Value 2: 14279731
Value 3: 14262533
Value 4: 14269749
Value 5: 14277560
Value 6: 14304037

Cela semble être une distribution très juste, n'est-ce pas?

Si j'ajoute Rand5 () plus ou moins (où le nombre de fois n'est pas divisible par 7), la distribution indique clairement les décalages. Par exemple, en ajoutant Rand5() 3 fois:

Value 0: 15199685
Value 1: 14402429
Value 2: 12795649
Value 3: 12796957
Value 4: 14402252
Value 5: 15202778
Value 6: 15200250

Donc, cela conduirait à ce qui suit:

public int Rand(int range) {
    int randomValue = 0;
    for(int i = 0; i < range; i++) {
        randomValue += Rand5();
    }
    return randomValue % range;

}

Et puis, je pourrais aller plus loin:

public static final int ORIGN_RANGE = 5;
public static final int DEST_RANGE  = 7;

@Test
public void testName() throws Exception {
    long times = 100000000;
    int indexes[] = new int[DEST_RANGE];
    for(int i = 0; i < times; i++) {
        int Rand7 = convertRand(DEST_RANGE, ORIGN_RANGE);
        indexes[Rand7]++;
    }

    for(int i = 0; i < DEST_RANGE; i++)
        System.out.println("Value " + i + ": " + indexes[i]);
}


public int convertRand(int destRange, int originRange) {
    int randomValue = 0;
    for(int i = 0; i < destRange; i++) {
        randomValue += Rand(originRange);
    }
    return randomValue % destRange;

}


public int Rand(int range) {
    return new Random().nextInt(range);
}

J'ai essayé ceci en remplaçant les propriétés destRange et originRange par diverses valeurs (même 7 pour Origin et 13 pour DEST), et j'obtiens cette distribution:

Value 0: 7713763
Value 1: 7706552
Value 2: 7694697
Value 3: 7695319
Value 4: 7688617
Value 5: 7681691
Value 6: 7674798
Value 7: 7680348
Value 8: 7685286
Value 9: 7683943
Value 10: 7690283
Value 11: 7699142
Value 12: 7705561

Ce que je peux conclure à partir de là, c'est que vous pouvez changer n'importe quel aléatoire en un autre en faisant la somme des temps de "destination" aléatoire d'origine. Cela donnera une sorte de distribution gaussienne (les valeurs moyennes étant plus probables et les valeurs Edge plus rares). Cependant, le module de destination semble se répartir uniformément sur cette distribution gaussienne ... Ce serait formidable de recevoir les commentaires d'un mathématicien ... 

Ce qui est cool, c’est que le coût est prévisible et constant à 100%, alors que d’autres solutions entraînent une faible probabilité de boucle infinie ...

0
Martin

Premièrement, je déplace ramdom5 () sur le 1 point 6 fois, pour obtenir 7 nombres aléatoires. Deuxièmement, j'ajoute 7 nombres pour obtenir une somme commune. Troisièmement, je reçois le reste de la division à 7 . Enfin, j'ajoute 1 pour obtenir les résultats de 1 à 7 . à l'exception de 1. 1 a une probabilité légèrement supérieure.

public int random7(){
    Random random = new Random();
    //function (1 + random.nextInt(5)) is given
    int random1_5 = 1 + random.nextInt(5); // 1,2,3,4,5
    int random2_6 = 2 + random.nextInt(5); // 2,3,4,5,6
    int random3_7 = 3 + random.nextInt(5); // 3,4,5,6,7
    int random4_8 = 4 + random.nextInt(5); // 4,5,6,7,8
    int random5_9 = 5 + random.nextInt(5); // 5,6,7,8,9
    int random6_10 = 6 + random.nextInt(5); //6,7,8,9,10
    int random7_11 = 7 + random.nextInt(5); //7,8,9,10,11

    //sumOfRandoms is between 28 and 56
    int sumOfRandoms = random1_5 + random2_6 + random3_7 + 
                       random4_8 + random5_9 + random6_10 + random7_11;
    //result is number between 0 and 6, and
    //equals 0 if sumOfRandoms = 28 or 35 or 42 or 49 or 56 , 5 options
    //equals 1 if sumOfRandoms = 29 or 36 or 43 or 50, 4 options
    //equals 2 if sumOfRandoms = 30 or 37 or 44 or 51, 4 options
    //equals 3 if sumOfRandoms = 31 or 38 or 45 or 52, 4 options
    //equals 4 if sumOfRandoms = 32 or 39 or 46 or 53, 4 options
    //equals 5 if sumOfRandoms = 33 or 40 or 47 or 54, 4 options
    //equals 6 if sumOfRandoms = 34 or 41 or 48 or 55, 4 options
    //It means that the probabilities of getting numbers between 0 and 6 are almost equal.
    int result = sumOfRandoms % 7;
    //we should add 1 to move the interval [0,6] to the interval [1,7]
    return 1 + result;
}
0
Michael Katkov

Voici le mien, cela tente de recréer Math.random() à partir de plusieurs appels de fonction Rand5(), en reconstruisant un intervalle d'unité (la plage de sortie de Math.random()) en le reconstruisant avec "des fractions pondérées" (?). Ensuite, en utilisant cet intervalle d'unité aléatoire pour produire un entier aléatoire compris entre 1 et 7:

function Rand5(){
  return Math.floor(Math.random()*5)+1;
}
function Rand7(){
  var uiRandom=0;
  var div=1;
  for(var i=0; i<7; i++){
    div*=5;
    var term=(Rand5()-1)/div;
    uiRandom+=term;
  }
  //return uiRandom;
  return Math.floor(uiRandom*7)+1; 
}

Pour paraphraser: Nous prenons un nombre entier aléatoire compris entre 0 et 4 (seulement Rand5()-1) et multiplions chaque résultat par 1/5, 1/25, 1/125, ... puis nous les additionnons. Cela ressemble au fonctionnement des fractions pondérées binaires; Je suppose plutôt que nous appellerons cela une fraction pondérée quinary (base-5): produisant un nombre compris entre 0 et 0,999999 sous la forme d'une série de (1/5) ^ n termes.

Modifier la fonction pour prendre n’importe quelle plage d’entiers aléatoires en entrée/sortie devrait être trivial. Et le code ci-dessus peut être optimisé lorsqu’il est réécrit en tant que fermeture.


Alternativement, nous pouvons aussi faire ceci:

function Rand5(){
  return Math.floor(Math.random()*5)+1;
}
function Rand7(){
  var buffer=[];
  var div=1;
  for (var i=0; i<7; i++){
    buffer.Push((Rand5()-1).toString(5));
    div*=5;
  }
  var n=parseInt(buffer.join(""),5);
  var uiRandom=n/div;
  //return uiRandom;
  return Math.floor(uiRandom*7)+1; 
}

Au lieu de jouer avec la construction de fractions pondérées quinaires (base 5), nous allons créer un nombre quinaire et le transformer en une fraction (0--0,9999 ... comme avant), puis calculer notre nombre aléatoire de 1 à 7 chiffres. De là.

Résultats ci-dessus (extrait de code n ° 2: 3 exécutions de 100 000 appels chacune):

1: 14263; 2: 14414; 3: 14249; 4: 14109; 5: 14217; 6: 14361; 7: 14387

1: 14205; 2: 14394; 3: 14238; 4: 14187; 5: 14384; 6: 14224; 7: 14368

1: 14425; 2: 14236; 3: 14334; 4: 14232; 5: 14160; 6: 14320; 7: 14293

0
jongo45

Pourquoi ne pas simplement diviser par 5 et multiplier par 7, puis arrondir? (Accordé, vous devrez utiliser des no. À virgule flottante)

C'est beaucoup plus facile et plus fiable (vraiment?) Que les autres solutions. Par exemple. en Python:

def ranndomNo7():
    import random
    Rand5 = random.randint(4)    # Produces range: [0, 4]
    Rand7 = int(Rand5 / 5 * 7)   # /5, *7, +0.5 and floor()
    return Rand7

N'était-ce pas facile?

0
Yatharth Agarwal

La solution simple a été bien couverte: prenez deux échantillons random5 pour un résultat random7 et recommencez si le résultat est en dehors de la plage générant une distribution uniforme. Si votre objectif est de réduire le nombre d'appels à random5, cela représente un gaspillage considérable - le nombre moyen d'appels à random5 pour chaque sortie de random7 est de 2,38 plutôt que 2, en raison du nombre d'échantillons jetés.

Vous pouvez faire mieux en utilisant plusieurs entrées random5 pour générer plusieurs sorties random7 à la fois. Pour les résultats calculés avec un entier de 31 bits, l'optimum est obtenu lorsque 12 appels à random5 sont générés pour générer 9 sorties random7, en prenant en moyenne 1,34 appels par sortie. C'est efficace car seuls 2018983 résultats sur 244140625 doivent être supprimés, soit moins de 1%.

Démo en Python:

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

def random7gen(n):
    count = 0
    while n > 0:
        samples = 6 * 7**9
        while samples >= 6 * 7**9:
            samples = 0
            for i in range(12):
                samples = samples * 5 + random5() - 1
                count += 1
        samples //= 6
        for outputs in range(9):
            yield samples % 7 + 1, count
            samples //= 7
            count = 0
            n -= 1
            if n == 0: break

>>> from collections import Counter
>>> Counter(x for x,i in random7gen(10000000))
Counter({2: 1430293, 4: 1429298, 1: 1428832, 7: 1428571, 3: 1428204, 5: 1428134, 6: 1426668})
>>> sum(i for x,i in random7gen(10000000)) / 10000000.0
1.344606
0
Mark Ransom

la conception principale de ce problème concerne la distribution normale, fournissant ici une solution simple et récursive à ce problème

supposons que nous ayons déjà Rand5() à notre portée:

def Rand7():
    # twoway = 0 or 1 in the same probability
    twoway = None
    while not twoway in (1, 2):
        twoway = Rand5()
    twoway -= 1

    ans = Rand5() + twoway * 5

    return ans if ans in range(1,8) else Rand7()

Explication

Nous pouvons diviser ce programme en 2 parties:

  1. bouclage de Rand5 () jusqu’à ce que nous trouvions 1 ou 2, cela signifie que nous avons 1/2 probabilité d’avoir 1 ou 2 dans la variable twoway
  2. composite ans by Rand5() + twoway * 5, ceci est exactement le résultat de Rand10(), si cela ne correspond pas à notre besoin (1 ~ 7), nous relançons Rand7.

P.S. nous ne pouvons pas exécuter directement une boucle while dans la deuxième partie car chaque probabilité de twoway doit être individuelle.

Cependant, en raison de la boucle while de la première section et de la récursivité de l'instruction return, cette fonction ne garantit pas le temps d'exécution, elle n'est en réalité pas efficace.

Résultat

J'ai fait un test simple pour observer la distribution à ma réponse.

result = [ Rand7() for x in xrange(777777) ]

ans = {
    1: 0,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: 0,
    7: 0,
}

for i in result:
    ans[i] += 1

print ans

Ça a donné

{1: 111170, 2: 110693, 3: 110651, 4: 111260, 5: 111197, 6: 111502, 7: 111304}

Par conséquent, nous pourrions savoir que cette réponse est dans une distribution normale.

Réponse simplifiée

Si vous ne vous souciez pas du temps d'exécution de cette fonction, voici une réponse simplifiée basée sur la réponse ci-dessus que j'ai donnée:

def Rand7():
    ans = Rand5() + (Rand5()-1) * 5
    return ans if ans < 8 else Rand7()

Cela augmente la probabilité de valeur qui est supérieure à 8 mais sera probablement la réponse la plus courte à ce problème.

0
Colin Su

Semblable à la réponse de Martin , mais a recours à l'élimination de l'entropie beaucoup moins fréquemment:

int Rand7(void) {
  static int m = 1;
  static int r = 0;

  for (;;) {
    while (m <= INT_MAX / 5) {
      r = r + m * (Rand5() - 1);
      m = m * 5;
    }
    int q = m / 7;
    if (r < q * 7) {
      int i = r % 7;
      r = r / 7;
      m = q;
      return i + 1;
    }
    r = r - q * 7;
    m = m - q * 7;
  }
}

Ici, nous construisons une valeur aléatoire entre 0 et m-1 et essayons de maximiser m en ajoutant autant d’états que possible sans dépassement de capacité (INT_MAX étant la plus grande valeur pouvant tenir dans une int en C, valeur qui a du sens dans votre langue et votre architecture).

Ensuite; si r se situe dans l'intervalle le plus grand possible divisible par 7, il contient un résultat viable. Nous pouvons diviser cet intervalle par 7, utiliser le reste comme résultat et renvoyer le reste de la valeur dans notre pool d'entropie. Sinon, r est dans l'autre intervalle qui ne se divise pas de manière égale et nous devons abandonner et redémarrer notre pool d'entropie à partir de cet intervalle inadéquat.

Comparé aux réponses populaires ici, il appelle Rand5() environ deux fois plus souvent en moyenne.

Les clivages peuvent être pris en compte dans des tours de bits triviales et des tables de correspondance pour la performance.

0
sh1

Cette expression est suffisante pour obtenir des entiers aléatoires compris entre 1 et 7.

int j = ( Rand5()*2 + 4 ) % 7 + 1;
0
user2713461

Cet algorithme réduit le nombre d'appels de Rand5 au minimum théorique de 7/5. L'appeler 7 fois en produisant les 5 prochains nombres de Rand7.

Il n'y a pas de rejet de bit aléatoire, et il n'y a PAS de possibilité d'attendre toujours le résultat.

#!/usr/bin/env Ruby

# random integer from 1 to 5
def Rand5
    STDERR.putc '.'
    1 + Rand( 5 )
end

@bucket = 0
@bucket_size = 0

# random integer from 1 to 7
def Rand7
    if @bucket_size == 0
        @bucket = 7.times.collect{ |d| Rand5 * 5**d }.reduce( &:+ )
        @bucket_size = 5
    end

    next_Rand7 = @bucket%7 + 1

    @bucket      /= 7
    @bucket_size -= 1

    return next_Rand7
end

35.times.each{ putc Rand7.to_s }
0
robermorales

Voici une solution qui essaie de minimiser le nombre d'appels à Rand5 () tout en gardant une implémentation simple et efficace. en particulier, il ne nécessite pas de grands nombres entiers arbitraires contrairement à la deuxième réponse d’Adam Rosenfield. Il exploite le fait que 23/19 = 1.21052 ... est une bonne approximation rationnelle pour log (7)/log (5) = 1.20906 ..., nous pouvons donc générer 19 éléments aléatoires de {1, ..., 7 } sur 23 éléments aléatoires de {1, ..., 5} par échantillonnage de rejet avec une faible probabilité de rejet. En moyenne, l'algorithme ci-dessous nécessite environ 1,266 appels à Rand5 () pour chaque appel de Rand7 (). Si la distribution de Rand5 () est uniforme, alors Rand7 ().

uint_fast64_t pool;

int capacity = 0;

void new_batch (void)
{
  uint_fast64_t r;
  int i;

  do {
    r = 0;
    for (i = 0; i < 23; i++)
      r = 5 * r + (Rand5() - 1);
  } while (r >= 11398895185373143ULL);  /* 7**19, a bit less than 5**23 */

  pool = r;
  capacity = 19;
}

int Rand7 (void)
{
  int r;

  if (capacity == 0)
    new_batch();

  r = pool % 7;
  pool /= 7;
  capacity--;

  return r + 1;
}
0
Emil Jeřábek