Je me demande quel serait le meilleur moyen (par exemple en Java) de générer des nombres aléatoires dans une plage particulière où chaque nombre a une certaine probabilité de se produire ou non.
par exemple.
Générez des entiers aléatoires à partir de [1; 3] avec les probabilités suivantes:
P (1) = 0,2
P (2) = 0,3
P (3) = 0,5
En ce moment, j’envisage la méthode pour générer un entier aléatoire dans [0; 100] et procéder comme suit:
Si c'est dans [0; 20] -> j'ai eu mon numéro aléatoire 1.
Si c'est dans [21; 50] -> j'ai eu mon numéro aléatoire 2.
Si c'est dans [51; 100] -> j'ai eu mon numéro aléatoire 3.
Que dirais-tu?
Le vôtre est déjà un très bon moyen et fonctionne bien avec n’importe quelle gamme.
Juste en train de penser: une autre possibilité consiste à supprimer les fractions en multipliant par un multiplicateur constant, puis à construire un tableau avec le size de ce multiplicateur. En multipliant par 10 vous obtenez
P(1) = 2
P(2) = 3
P(3) = 5
Ensuite, vous créez un tableau avec les valeurs inverses - '1' entre les éléments 1 et 2, '2' entre 3 et 6, et ainsi de suite:
P = (1,1, 2,2,2, 3,3,3,3,3);
et vous pouvez alors choisir un élément aléatoire de ce tableau à la place.
(Ajouter.) En utilisant les probabilités de l'exemple dans le commentaire de kiruwka:
int[] numsToGenerate = new int[] { 1, 2, 3, 4, 5 };
double[] discreteProbabilities = new double[] { 0.1, 0.25, 0.3, 0.25, 0.1 };
le plus petit multiplicateur qui mène à tous les entiers est 20, ce qui vous donne
2, 5, 6, 5, 2
et donc la longueur de numsToGenerate
serait 20, avec les valeurs suivantes:
1 1
2 2 2 2 2
3 3 3 3 3 3
4 4 4 4 4
5 5
La distribution est exactement identique - la chance de '1', par exemple, est maintenant 2 sur 20 - toujours 0,1.
Ceci est basé sur vos probabilités d'origine totalisant toutes 1. Si ce n'est pas le cas, multipliez le total par ce même facteur (qui sera alors aussi la longueur de votre tableau).
Il y a quelque temps, j'ai écrit une classe d'assistance pour résoudre ce problème. Le code source devrait montrer le concept assez clairement:
public class DistributedRandomNumberGenerator {
private Map<Integer, Double> distribution;
private double distSum;
public DistributedRandomNumberGenerator() {
distribution = new HashMap<>();
}
public void addNumber(int value, double distribution) {
if (this.distribution.get(value) != null) {
distSum -= this.distribution.get(value);
}
this.distribution.put(value, distribution);
distSum += distribution;
}
public int getDistributedRandomNumber() {
double Rand = Math.random();
double ratio = 1.0f / distSum;
double tempDist = 0;
for (Integer i : distribution.keySet()) {
tempDist += distribution.get(i);
if (Rand / ratio <= tempDist) {
return i;
}
}
return 0;
}
}
L'utilisation de la classe est la suivante:
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.3d); // Adds the numerical value 1 with a probability of 0.3 (30%)
// [...] Add more values
int random = drng.getDistributedRandomNumber(); // Generate a random number
Pilote de test pour vérifier la fonctionnalité:
public static void main(String[] args) {
DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(2, 0.3d);
drng.addNumber(3, 0.5d);
int testCount = 1000000;
HashMap<Integer, Double> test = new HashMap<>();
for (int i = 0; i < testCount; i++) {
int random = drng.getDistributedRandomNumber();
test.put(random, (test.get(random) == null) ? (1d / testCount) : test.get(random) + 1d / testCount);
}
System.out.println(test.toString());
}
Exemple de sortie pour ce pilote de test:
{1=0.20019100000017953, 2=0.2999349999988933, 3=0.4998739999935438}
Vous avez déjà écrit l'implémentation dans votre question. ;)
final int ran = myRandom.nextInt(100);
if (ran > 50) { return 3; }
else if (ran > 20) { return 2; }
else { return 1; }
Vous pouvez accélérer ceci pour des implémentations plus complexes en calculant le résultat sur un tableau de commutation comme ceci:
t[0] = 1; t[1] = 1; // ... one for each possible result
return t[ran];
Mais cela ne devrait être utilisé que s'il s'agit d'un goulot d'étranglement lié aux performances et appelé plusieurs centaines de fois par seconde.
Si vous rencontrez des problèmes de performances au lieu de rechercher toutes les n valeurs O(n)
vous pouvez effectuer une recherche binaire qui coûte O (log n)
Random r=new Random();
double[] weights=new double[]{0.1,0.1+0.2,0.1+0.2+0.5};
// end of init
double random=r.nextDouble();
// next perform the binary search in weights array
vous devez uniquement accéder à log2 (poids. longueur) en moyenne si vous avez beaucoup d'éléments de poids.
Votre approche convient aux nombres que vous avez choisis, mais vous pouvez réduire le stockage en utilisant un tableau de 10 au lieu de 100. Toutefois, cette approche ne permet pas de généraliser à un grand nombre de résultats ou de résultats avec des probabilités telles que 1/e
ou 1/PI
.
Une solution potentiellement meilleure consiste à utiliser un tableau alias . La méthode des alias nécessite un travail de O(n)
pour configurer la table pour les résultats de n
, mais le temps de génération est constant, quel que soit le nombre de résultats obtenus.
Essayez ceci: Dans cet exemple, j'utilise un tableau de caractères, mais vous pouvez le remplacer par votre tableau entier.
La liste de poids contient pour chaque caractère la probabilité associée .. .. Elle représente la distribution de probabilité de mon jeu de caractères.
Dans la liste de pondération pour chaque caractère, i stockait sa probabilité réelle plus la somme de toute probabilité antécédente.
Par exemple, dans le poids, le troisième élément correspondant à 'C' est 65:
P ('A') + P ('B) + P (' C ') = P (X => c)
10 + 20 + 25 = 65
Donc weightsum représente la distribution cumulative de mon jeu de caractères . Weightsum contient les valeurs suivantes:
Il est facile de voir que le 8ème élément correspondant à H, a un écart plus important (80 évidemment comme sa probabilité) alors ressemble plus à arriver!
List<Character> charset = Arrays.asList('A','B','C','D','E','F','G','H','I','J');
List<Integer> weight = Arrays.asList(10,30,25,60,20,70,10,80,20,30);
List<Integer> weightsum = new ArrayList<>();
int i=0,j=0,k=0;
Random Rnd = new Random();
weightsum.add(weight.get(0));
for (i = 1; i < 10; i++)
weightsum.add(weightsum.get(i-1) + weight.get(i));
Ensuite, j'utilise un cycle pour obtenir 30 extractions de caractères aléatoires à partir du jeu de caractères, chacune étant dessinée en fonction de la probabilité cumulée.
Dans k i stocke un nombre aléatoire compris entre 0 et la valeur maximale allouée dans poids ..... Je cherche ensuite dans poids pour un nombre supérieur à k, la position du nombre dans poids correspond à la même position du caractère dans charset.
for (j = 0; j < 30; j++)
{
Random r = new Random();
k = r.nextInt(weightsum.get(weightsum.size()-1));
for (i = 0; k > weightsum.get(i); i++) ;
System.out.print(charset.get(i));
}
Le code donne cette séquence de caractères:
HHFAIIDFBDDDDHFICJHACCDFJBGBHHB
Faisons le calcul!
A = 2
B = 4
C = 3
D = 5
E = 0
F = 4
G = 1
H = 6
I = 3
J = 2
Total.:30
Comme nous le souhaitons, D et H ont plus d’occurrences (prob. 70% et 80%)
Otherwinse E n'est pas sorti du tout. (10% prob.)
Voici le code python même si vous demandez Java, mais il est très similaire.
# weighted probability
theta = np.array([0.1,0.25,0.6,0.05])
print(theta)
sample_axis = np.hstack((np.zeros(1), np.cumsum(theta)))
print(sample_axis)
[0. 0,1 0,35 0,95 1.]. Ceci représente la distribution cumulative.
vous pouvez utiliser une distribution uniforme pour dessiner un index dans cette plage d'unités.
def binary_search(axis, q, s, e):
if e-s <= 1:
print(s)
return s
else:
m = int( np.around( (s+e)/2 ) )
if q < axis[m]:
binary_search(axis, q, s, m)
else:
binary_search(axis, q, m, e)
range_index = np.random.Rand(1)
print(range_index)
q = range_index
s = 0
e = sample_axis.shape[0]-1
binary_search(sample_axis, q, 0, e)
Écrit cette classe pour un entretien après avoir référencé le document désigné par pjs dans un autre post , la population de la table base64 peut être optimisée. Le résultat est incroyablement rapide, l'initialisation coûte un peu cher, mais si les probabilités ne changent pas souvent, il s'agit d'une bonne approche.
* Pour la clé en double, la dernière probabilité est prise au lieu d'être combinée (comportement légèrement différent du comportement EnumeratedIntegerDistribution)
public class RandomGen5 extends BaseRandomGen {
private int[] t_array = new int[4];
private int sumOfNumerator;
private final static int DENOM = (int) Math.pow(2, 24);
private static final int[] bitCount = new int[] {18, 12, 6, 0};
private static final int[] cumPow64 = new int[] {
(int) ( Math.pow( 64, 3 ) + Math.pow( 64, 2 ) + Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
(int) ( Math.pow( 64, 2 ) + Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
(int) ( Math.pow( 64, 1 ) + Math.pow( 64, 0 ) ),
(int) ( Math.pow( 64, 0 ) )
};
ArrayList[] base64Table = {new ArrayList<Integer>()
, new ArrayList<Integer>()
, new ArrayList<Integer>()
, new ArrayList<Integer>()};
public int nextNum() {
int Rand = (int) (randGen.nextFloat() * sumOfNumerator);
for ( int x = 0 ; x < 4 ; x ++ ) {
if (Rand < t_array[x])
return x == 0 ? (int) base64Table[x].get(Rand >> bitCount[x])
: (int) base64Table[x].get( ( Rand - t_array[x-1] ) >> bitCount[x]) ;
}
return 0;
}
public void setIntProbList( int[] intList, float[] probList ) {
Map<Integer, Float> map = normalizeMap( intList, probList );
populateBase64Table( map );
}
private void clearBase64Table() {
for ( int x = 0 ; x < 4 ; x++ ) {
base64Table[x].clear();
}
}
private void populateBase64Table( Map<Integer, Float> intProbMap ) {
int startPow, decodedFreq, table_index;
float rem;
clearBase64Table();
for ( Map.Entry<Integer, Float> numObj : intProbMap.entrySet() ) {
rem = numObj.getValue();
table_index = 3;
for ( int x = 0 ; x < 4 ; x++ ) {
decodedFreq = (int) (rem % 64);
rem /= 64;
for ( int y = 0 ; y < decodedFreq ; y ++ ) {
base64Table[table_index].add( numObj.getKey() );
}
table_index--;
}
}
startPow = 3;
for ( int x = 0 ; x < 4 ; x++ ) {
t_array[x] = x == 0 ? (int) ( Math.pow( 64, startPow-- ) * base64Table[x].size() )
: ( (int) ( ( Math.pow( 64, startPow-- ) * base64Table[x].size() ) + t_array[x-1] ) );
}
}
private Map<Integer, Float> normalizeMap( int[] intList, float[] probList ) {
Map<Integer, Float> tmpMap = new HashMap<>();
Float mappedFloat;
int numerator;
float normalizedProb, distSum = 0;
//Remove duplicates, and calculate the sum of non-repeated keys
for ( int x = 0 ; x < probList.length ; x++ ) {
mappedFloat = tmpMap.get( intList[x] );
if ( mappedFloat != null ) {
distSum -= mappedFloat;
} else {
distSum += probList[x];
}
tmpMap.put( intList[x], probList[x] );
}
//Normalise the map to key -> corresponding numerator by multiplying with 2^24
sumOfNumerator = 0;
for ( Map.Entry<Integer, Float> intProb : tmpMap.entrySet() ) {
normalizedProb = intProb.getValue() / distSum;
numerator = (int) ( normalizedProb * DENOM );
intProb.setValue( (float) numerator );
sumOfNumerator += numerator;
}
return tmpMap;
}
}
Si vous n'êtes pas contre l'ajout d'une nouvelle bibliothèque dans votre code, cette fonctionnalité est déjà implémentée dans MockNeat , vérifiez la méthode probabilities () .
Quelques exemples directement à partir du wiki:
String s = mockNeat.probabilites(String.class)
.add(0.1, "A") // 10% chance
.add(0.2, "B") // 20% chance
.add(0.5, "C") // 50% chance
.add(0.2, "D") // 20% chance
.val();
Ou si vous voulez générer des nombres dans des intervalles donnés avec une probabilité donnée, vous pouvez faire quelque chose comme:
Integer x = m.probabilites(Integer.class)
.add(0.2, m.ints().range(0, 100))
.add(0.5, m.ints().range(100, 200))
.add(0.3, m.ints().range(200, 300))
.val();
Déni de responsabilité: je suis l'auteur de la bibliothèque, je peux donc être partial lorsque je le recommande.
Également répondu ici: trouver un pays aléatoire, mais la probabilité de choisir un pays plus peuplé devrait être plus élevée . Utiliser TreeMap:
TreeMap<Integer, Integer> map = new TreeMap<>();
map.put(percent1, 1);
map.put(percent1 + percent2, 2);
// ...
int random = (new Random()).nextInt(100);
int result = map.ceilingEntry(random).getValue();