Je voudrais sélectionner au hasard un élément d'un tableau, mais chaque élément a une probabilité de sélection connue.
Toutes les chances ensemble (dans le tableau) totalisent 1.
Quel algorithme suggéreriez-vous comme le plus rapide et le plus approprié pour des calculs énormes?
Exemple:
id => chance
array[
0 => 0.8
1 => 0.2
]
pour ce pseudocode, l'algorithme en question devrait sur plusieurs appels retourner statistiquement quatre éléments sur id 0
pour un élément sur id 1
.
Calculez la fonction de densité cumulative discrète (CDF) de votre liste - ou, en termes simples, le tableau des sommes cumulées des poids. Générez ensuite un nombre aléatoire compris entre 0 et la somme de tous les poids (peut-être 1 dans votre cas), effectuez une recherche binaire pour trouver ce nombre aléatoire dans votre tableau CDF discret et obtenez la valeur correspondant à cette entrée - ce est votre nombre aléatoire pondéré.
L'algorithme est simple
Rand_no = Rand(0,1)
for each element in array
if(Rand_num < element.probablity)
select and break
Rand_num = Rand_num - element.probability
J'ai trouvé cet article pour être le plus utile pour bien comprendre ce problème. Cette question de stackoverflow peut également être ce que vous recherchez.
Je pense que la solution optimale consiste à utiliser la méthode Alias (wikipedia) . Il faut O (n) temps pour initialiser, O (1) temps pour faire une sélection, et O (n) mémoire.
Voici l'algorithme pour générer le résultat du laminage d'un dé pondéré n (à partir d'ici, il est trivial de sélectionner un élément à partir d'une longueur - n tableau) comme tiré de cet article . L'auteur suppose que vous avez des fonctions pour lancer un dé juste (floor(random() * n)
) et lancer une pièce biaisée (random() < p
).
Algorithme: méthode d'alias de Vose
Initialisation:
- Créer des tableaux Alias et Prob , chacun de taille n .
- Créez deux listes de travail, Petite et Grande .
- Multipliez chaque probabilité par n .
- Pour chaque probabilité mise à l'échelle pje:
- Si pje <1 , ajoutez i à Petit .
- Sinon ( pje ≥ 1 ), ajoutez i à Grand .
- Alors que Petit et Grand ne sont pas vides: ( Large peut être vidé en premier)
- Supprimez le premier élément de Small ; appelez-le l .
- Supprimez le premier élément de Large ; appelez-le g .
- Set Prob [l] = pl.
- Définissez Alias [l] = g .
- Définissez pg : = (pg+ pl) -1 . (Il s'agit d'une option plus stable numériquement.)
- Si pg<1 , ajoutez g à Petit .
- Sinon ( pg ≥ 1 ), ajoutez g à Grand .
- Alors que Large n'est pas vide:
- Supprimez le premier élément de Large ; appelez-le g .
- Définissez Prob [g] = 1 .
- Alors que Petit n'est pas vide: Ceci n'est possible qu'en raison de l'instabilité numérique.
- Supprimez le premier élément de Small ; appelez-le l .
- Définissez Prob [l] = 1 .
Génération:
- Générez un jet de dé équitable à partir d'un dé n ; appeler le côté i .
- Lancez une pièce biaisée qui arrive face à face avec probabilité Prob [i] .
- Si la pièce sort "têtes", retournez i .
- Sinon, retournez Alias [i] .
Cela peut être fait en O(1) temps prévu par échantillon comme suit.
Calculez le CDF F(i) pour chaque élément i comme étant la somme des probabilités inférieure ou égale à i.
Définissez la plage r(i) d'un élément i comme étant l'intervalle [F (i - 1), F (i)].
Pour chaque intervalle [(i - 1)/n, i/n], créez un compartiment composé de la liste des éléments dont la plage chevauche l'intervalle. Cela prend O(n) temps au total pour le tableau complet tant que vous êtes raisonnablement prudent.
Lorsque vous échantillonnez au hasard le tableau, vous calculez simplement dans quel compartiment le nombre aléatoire se trouve et comparez avec chaque élément de la liste jusqu'à ce que vous trouviez l'intervalle qui le contient.
Le coût d'un échantillon est O (la longueur attendue d'une liste choisie au hasard) <= 2.
Un exemple en Ruby
#each element is associated with its probability
a = {1 => 0.25 ,2 => 0.5 ,3 => 0.2, 4 => 0.05}
#at some point, convert to ccumulative probability
acc = 0
a.each { |e,w| a[e] = acc+=w }
#to select an element, pick a random between 0 and 1 and find the first
#cummulative probability that's greater than the random number
r = Rand
selected = a.find{ |e,w| w>r }
p selected[0]
Un autre Ruby exemple:
def weighted_Rand(weights = {})
raise 'Probabilities must sum up to 1' unless weights.values.inject(&:+) == 1.0
raise 'Probabilities must not be negative' unless weights.values.all? { |p| p >= 0 }
# Do more sanity checks depending on the amount of trust in the software component using this method
# E.g. don't allow duplicates, don't allow non-numeric values, etc.
# Ignore elements with probability 0
weights = weights.reject { |k, v| v == 0.0 } # e.g. => {"a"=>0.4, "b"=>0.4, "c"=>0.2}
# Accumulate probabilities and map them to a value
u = 0.0
ranges = weights.map { |v, p| [u += p, v] } # e.g. => [[0.4, "a"], [0.8, "b"], [1.0, "c"]]
# Generate a (pseudo-)random floating point number between 0.0(included) and 1.0(excluded)
u = Rand # e.g. => 0.4651073966724186
# Find the first value that has an accumulated probability greater than the random number u
ranges.find { |p, v| p > u }.last # e.g. => "b"
end
Comment utiliser:
weights = {'a' => 0.4, 'b' => 0.4, 'c' => 0.2, 'd' => 0.0}
weighted_Rand weights
Quoi attendre:
d = 1000.times.map{ weighted_Rand weights }
d.count('a') # 396
d.count('b') # 406
d.count('c') # 198
Solution Ruby utilisant le gemme de ramassage :
require 'pickup'
chances = {0=>80, 1=>20}
picker = Pickup.new(chances)
Exemple:
5.times.collect {
picker.pick(5)
}
a donné la sortie:
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 1]]
Ceci est un code PHP que j'ai utilisé dans la production:
/**
* @return \App\Models\CdnServer
*/
protected function selectWeightedServer(Collection $servers)
{
if ($servers->count() == 1) {
return $servers->first();
}
$totalWeight = 0;
foreach ($servers as $server) {
$totalWeight += $server->getWeight();
}
// Select a random server using weighted choice
$randWeight = mt_Rand(1, $totalWeight);
$accWeight = 0;
foreach ($servers as $server) {
$accWeight += $server->getWeight();
if ($accWeight >= $randWeight) {
return $server;
}
}
}
Si le tableau est petit, je lui donnerais une longueur de, dans ce cas, cinq et attribuerais les valeurs appropriées:
array[
0 => 0
1 => 0
2 => 0
3 => 0
4 => 1
]
l'astuce pourrait être d'échantillonner un tableau auxiliaire avec des répétitions d'éléments qui reflètent la probabilité
Compte tenu des éléments associés à leur probabilité, en pourcentage:
h = {1 => 0.5, 2 => 0.3, 3 => 0.05, 4 => 0.05 }
auxiliary_array = h.inject([]){|memo,(k,v)| memo += Array.new((100*v).to_i,k) }
Ruby-1.9.3-p194 > auxiliary_array
=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]
auxiliary_array.sample
si vous voulez être aussi générique que possible, vous devez calculer le multiplicateur en fonction du nombre maximum de chiffres fractionnaires et l'utiliser à la place de 100:
m = 10**h.values.collect{|e| e.to_s.split(".").last.size }.max
Je vais améliorer https://stackoverflow.com/users/626341/masciugo réponse.
Fondamentalement, vous créez un grand tableau où le nombre de fois qu'un élément apparaît est proportionnel au poids.
Elle présente certains inconvénients.
Pour contrer cela, c'est ce que vous faites.
Créez un tel tableau, mais insérez uniquement un élément au hasard. La probabilité qu'un élément soit inséré est proportionnelle au poids.
Sélectionnez ensuite un élément aléatoire de l'ordinaire.
Donc, s'il y a 3 éléments avec différents poids, vous choisissez simplement un élément dans un tableau de 1 à 3 éléments.
Des problèmes peuvent survenir si l'élément construit est vide. C'est-à-dire qu'il arrive juste qu'aucun élément n'apparaisse dans le tableau parce que leurs dés roulent différemment.
Dans ce cas, je propose que la probabilité qu'un élément soit inséré est p (inséré) = wi/wmax.
De cette façon, un élément, à savoir celui qui a la plus forte probabilité, sera inséré. Les autres éléments seront insérés par la probabilité relative.
Disons que nous avons 2 objets.
l'élément 1 apparaît 0,20% du temps. l'élément 2 affiche 0,40% du temps et présente la probabilité la plus élevée.
Dans le tableau, l'élément 2 apparaîtra tout le temps. L'élément 1 apparaîtra la moitié du temps.
L'élément 2 sera donc appelé 2 fois plus que l'élément 1. Pour la généralité, tous les autres éléments seront appelés proportionnels à leur poids. De plus, la somme de toutes leurs probabilités est 1 car le tableau aura toujours au moins 1 élément.
J'imagine que des nombres supérieurs ou égaux à 0,8 mais inférieurs à 1,0 sélectionnent le troisième élément.
En d'autres termes:
x est un nombre aléatoire compris entre 0 et 1
si 0,0> = x <0,2: élément 1
si 0,2> = x <0,8: élément 2
si 0,8> = x <1,0: élément 3