J'ai une boîte de butin que je veux remplir avec un élément aléatoire. Mais je veux que chaque article ait une chance différente d'être choisi. Par exemple:
- 5% de chance de 10 pièces d'or
- 20% de chance d'épée
- 45% de chances de bouclier
- 20% de chance d'armure
- 10% de chance de potion
Comment puis-je faire en sorte que je sélectionne exactement l'un des éléments ci-dessus, où ces pourcentages représentent les chances respectives d'obtenir le butin?
Réponses:
La solution de probabilités codées de manière douce
La solution de probabilité codée en dur présente l'inconvénient de devoir définir les probabilités dans votre code. Vous ne pouvez pas les déterminer au moment de l'exécution. Il est également difficile à maintenir.
Voici une version dynamique du même algorithme.
Voici un exemple d'implémentation en Java sous la forme d'une classe de modèle que vous pouvez instancier pour tout objet utilisé par votre jeu. Vous pouvez ensuite ajouter des objets avec la méthode
.addEntry(object, relativeWeight)
et sélectionner l’une des entrées ajoutées précédemment avec.get()
Usage:
Voici la même classe implémentée en C # pour votre projet Unity, XNA ou MonoGame:
Et en voici un en JavaScript :
Pro:
Contra:
O(n)
complexité d'exécution). Ainsi, lorsque vous avez un très grand nombre d’articles et que vous dessinez très souvent, cela risque de devenir lent. Une optimisation simple consiste à placer les éléments les plus probables en premier afin que l'algorithme se termine tôt dans la plupart des cas. Une optimisation plus complexe consiste à exploiter le fait que le tableau est trié et à effectuer une recherche par bissection. Cela ne prend que duO(log n)
temps.O(n)
pire temps d'exécution)la source
Remarque: j'ai créé une bibliothèque C # pour ce problème précis.
Les autres solutions sont acceptables si vous ne disposez que d’un petit nombre d’articles et que vos probabilités ne changent jamais. Cependant, avec de nombreux éléments ou des probabilités changeantes (par exemple, supprimer des éléments après les avoir sélectionnés) , vous souhaiterez un système plus puissant.
Voici les deux solutions les plus courantes (les deux sont inclus dans la bibliothèque ci-dessus)
Méthode du pseudonyme du marcheur
Une solution intelligente extrêmement rapide (
O(1)
!) Si vos probabilités sont constantes. En substance, l'algorithme crée un jeu de fléchettes 2D ("table d'alias") à partir de vos probabilités et lance une fléchette dessus.Il existe de nombreux articles en ligne sur le fonctionnement de ce logiciel si vous souhaitez en savoir plus.
Le seul problème est que si vos probabilités changent, vous devez régénérer la table des alias, qui est lente. Par conséquent, si vous devez supprimer des éléments une fois qu'ils ont été sélectionnés, ce n'est pas la solution.
Solution basée sur l'arbre
L’autre solution courante consiste à créer un tableau dans lequel chaque élément stocke la somme de sa probabilité et de tous les éléments qui le précèdent. Ensuite, générez simplement un nombre aléatoire à partir de [0,1) et effectuez une recherche binaire pour savoir où ce nombre se situe dans la liste.
Cette solution est très facile à coder / comprendre, mais la sélection est plus lente que la méthode Alias de Walker et la modification des probabilités le reste
O(n)
. Nous pouvons l’améliorer en transformant le tableau en un arbre de recherche binaire, dans lequel chaque nœud garde une trace de la somme des probabilités dans tous les éléments de sa sous-arborescence. Ensuite, lorsque nous générons le nombre à partir de [0,1), nous pouvons simplement parcourir l’arbre pour trouver l’article qu’il représente.Cela nous donne
O(log n)
de choisir un article et de changer les probabilités! Cela rendNextWithRemoval()
extrêmement rapide!Les resultats
Voici quelques repères rapides de la bibliothèque ci-dessus, en comparant ces deux approches
Comme vous pouvez le constater, dans le cas particulier de probabilités statiques (non changeantes), la méthode Alias de Walker est environ 50-100% plus rapide. Mais dans les cas les plus dynamiques, l'arbre est plus rapide de plusieurs ordres de grandeur !
la source
nlog(n)
) lors du tri des éléments en fonction du poids.La roue de la solution de fortune
Vous pouvez utiliser cette méthode lorsque les probabilités de votre groupe d'éléments ont un dénominateur commun assez important et que vous devez en tirer très souvent.
Créez un tableau d'options. Mais mettez-y chaque élément plusieurs fois, avec le nombre de doublons de chaque élément proportionnel à ses chances d'apparition. Pour l'exemple ci-dessus, tous les éléments ont des probabilités multiplicateurs de 5%, vous pouvez donc créer un tableau de 20 éléments comme celui-ci:
Ensuite, choisissez simplement un élément aléatoire de cette liste en générant un entier aléatoire compris entre 0 et la longueur du tableau - 1.
Désavantages:
Avantages:
la source
Epic Scepter of the Apocalypse
. Une telle approche à deux niveaux exploite les avantages des deux approches.[('gold', 1),('sword',4),...]
résumer tous les poids, puis de faire rouler un nombre aléatoire de 0 à la somme, puis de réitérer le tableau et de calculer où le nombre aléatoire atterrit (c.-à-d. areduce
). Fonctionne bien pour les tableaux qui sont mis à jour souvent et sans problème de mémoire important.La solution de probabilités codées en dur
Le moyen le plus simple de trouver un élément aléatoire dans une collection pondérée consiste à parcourir une chaîne d'instructions if-else, où chaque if-else augmente probablement, étant donné que le précédent ne touche pas.
La raison pour laquelle les conditionnels sont égaux à sa chance, plus toutes les chances conditionnelles précédentes, c'est parce que les conditionnels précédents ont déjà éliminé la possibilité qu'il s'agisse de ces éléments. Donc, pour le bouclier conditionnel
else if(rand <= 70)
, 70 est égal à 45% de chance du bouclier, plus les 5% de chance de l'or et 20% de chance de l'épée.Avantages:
Désavantages:
la source
En C #, vous pouvez utiliser un scan Linq pour exécuter votre accumulateur afin de vérifier un nombre aléatoire compris entre 0 et 100.0f et .First () à obtenir. Donc, comme une ligne de code.
Donc, quelque chose comme:
sum
est un entier zéro initialisé eta
une liste de structures prob / item / tuples / instances.rand
est un nombre aléatoire précédemment généré dans la plage.Cela accumule simplement la somme sur la liste des plages jusqu'à ce qu'il dépasse le nombre aléatoire précédemment sélectionné, et renvoie soit l'élément, soit la valeur null, où la valeur null serait renvoyée si la plage de nombres aléatoires (par exemple 100) est inférieure à la plage de pondération totale par erreur , et le nombre aléatoire sélectionné est en dehors de la plage de pondération totale.
Cependant, vous remarquerez que les poids dans OP correspondent étroitement à une distribution normale (courbe de Bell). Je pense qu'en général, vous ne voudrez pas de plages spécifiques, vous aurez tendance à vouloir une distribution qui se rétrécit autour d'une courbe en cloche ou simplement sur une courbe exponentielle décroissante (par exemple). Dans ce cas, vous pouvez simplement utiliser une formule mathématique pour générer un index dans un tableau d'éléments, triés par ordre de probabilité privilégiée. Un bon exemple est CDF dans la distribution normale
Aussi un exemple ici .
Un autre exemple est que vous pouvez prendre une valeur aléatoire de 90 degrés à 180 degrés pour obtenir le quadrant inférieur droit d'un cercle, prenez la composante x à l'aide de cos (r) et utilisez-la pour l'indexer dans une liste hiérarchisée.
Avec différentes formules, vous pouvez avoir une approche générale dans laquelle vous introduisez simplement une liste hiérarchisée de n'importe quelle longueur (par exemple N) et mappez le résultat de la formule (par exemple: cos (x) est compris entre 0 et 1) par multiplication (par exemple: Ncos (x ) = 0 à N) pour obtenir l'index.
la source
Les probabilités n'ont pas besoin d'être codées en dur. Les éléments et les seuils peuvent être ensemble dans un tableau.
Vous devez toujours accumuler les seuils, mais vous pouvez le faire lors de la création d'un fichier de paramètres au lieu de le coder.
la source
random()
dans la boucle?J'ai fait cette fonction: https://github.com/thewheelmaker/GDscript_Weighted_Random Now! dans votre cas, vous pouvez l'utiliser comme ceci:
Il donne juste un nombre entre 0 et 4 mais vous pouvez le mettre dans un tableau où vous avez obtenu les éléments.
Ou en fonction:
Voici le code. Je l'ai fait sur GDscript, vous pouvez, mais cela peut modifier une autre langue, vérifiez également les erreurs de logique:
Cela fonctionne comme ceci: on_normal_case ([50,50], 0) Ceci donne 0 ou 1, la probabilité est la même.
on_normal_case ([50,50], 1) Ceci donne 1 ou 2, il a la même probabilité les deux.
on_normal_case ([20,80], 1) Ceci donne 1 ou 2, le changement est plus important pour obtenir deux.
on_normal_case ([20,80,20,20,30], 1) Ceci donne des nombres aléatoires allant de 1 à 5 et les plus grands sont plus susceptibles que les plus petits.
on_normal_case ([20,80,0,0,20,20,30,0,0,0,0,3,33], 45) Ce lancer se lance entre les nombres 45,46,49,50,51,56 vous voyez quand il y a est zéro il ne se produit jamais.
Ainsi, sa fonction ne renvoie qu’un nombre aléatoire qui dépend de la longueur de ce tableau et de ce nombre de transformation, et ints dans le tableau sont des poids de probabilité qu’un nombre puisse se produire, où ce nombre est l’emplacement du tableau, plus un nombre de transformation.
la source