lets say, I would like to trigger a function by percentage chance
function A () { console.log('A triggered'); } //50% chance to trigger
if (Math.random() >= 0.5) A();
now i would like to add in more function to be trigger by chances, what i did was
//method 1
function B () { console.log('B triggered'); } //10% chance to trigger
function C () { console.log('C triggered'); } //10% chance to trigger
if (Math.random() >= 0.5) {
A();
} else if (Math.random() > (0.5 + 0.1)) {
B();
} else if (Math.random() > (0.5 + 0.1 + 0.1)) {
C();
}
But this make A()
get priority before B() and C()
. Therefore I change the code into
//method 2
var randNumber = Math.random();
if (randNumber <= 0.5) { A(); }
else if (randNumber > 0.5 && randNumber <= (0.5 + 0.1)) { B(); }
else if (randNumber > (0.5 + 0.1) && randNumber <= (0.5 + 0.1 + 0.1)) { C(); }
This method looks fair but it looks inefficient because it need to be hardcoded chances in every single if else
function and if I have a list long of function and triggering chances, I will need to make the if else
very long and messy
Question is is there any method I can done this better and efficient? unlike these 2 method i shown above.
*also fair and square is important as well (sound like a game)
Sorry if I explain the situation badly, Any help to this will be appreciated. Thanks.
You could create a list of the functions you want to call and their chance to be called. Then you divided the range of the random numbers into chunks using the chances. In this example it would be:
0.0 0.5 0.6 0.7
0.5/A 0.1/B 0.1/C
The entries do not need to be sorted based on the chances. If the sum of the chances is larger then 1.0 then the last elements of the array won't be called. That's something you need to ensure yourself with this approach.
The code could look like this:
function A() {
console.log('A was called')
}
function B() {
console.log('B was called')
}
function C() {
console.log('C was called')
}
var list = [
{chance: 0.5, func: A},
{chance: 0.1, func: B},
{chance: 0.1, func: C}
];
function callRandomFunction(list) {
var rand = Math.random() // get a random number between 0 and 1
var accumulatedChance = 0 // used to figure out the current
var found = list.find(function(element) { // iterate through all elements
accumulatedChance += element.chance // accumulate the chances
return accumulatedChance >= rand // tests if the element is in the range and if yes this item is stored in 'found'
})
if( found ) {
console.log('match found for: ' + rand)
found.func()
} else {
console.log('no match found for: ' + rand)
}
}
callRandomFunction(list)
This method looks fair but it looks inefficient because it need to be hardcoded chances in every single
if else
That's true, though the lower bound is unnecessary, since you're using else
. Also, using the math makes it look more complicated than it is:
var randNumber = Math.random();
if (randNumber <= 0.5) { A(); }
else if (randNumber <= 0.6) { B(); }
else if (randNumber <= 0.7) { C(); }
If the interval between them is fixed, you can avoid those hardcoded values:
var randNumber = Math.random();
var chance = 0.5;
var interval = 0.1;
if (randNumber <= chance) { A(); }
else if (randNumber <= (chance += interval)) { B(); }
else if (randNumber <= (chance += interval)) { C(); }
Or actually, I suppose even if it isn't:
var randNumber = Math.random();
var chance = 0.5;
if (randNumber <= chance) { A(); }
else if (randNumber <= (chance += 0.1)) { B(); }
else if (randNumber <= (chance += 0.2)) { C(); } // Intentionally different from
// yours to show different spacing
Another way to do it would be to convert to only numbers that will reliably end up as predictable strings¹ (say, 0-9) and use a dispatch object:
var dispatch = {
0: A,
1: A,
2: A,
3: A,
4: A,
5: B,
6: C
};
var f = dispatch[Math.floor(Math.random() * 10)];
if (f) {
f();
}
Note that although written with numeric literals above, the names of the properties of the dispatch
object are actually strings ("0"
, "1"
, etc.).
t.niese's approach with a list is better than this dispatch object, though.
¹ Basically this means avoiding (some) fractional values and staying in a reasonable numeric range. For instance, if you had a calculation that ended up doing something like 0.009 / 3
and you (reasonably) expected to be able to use "0.003"
as your key, it wouldn't work, because String(0.009 / 3)
is "0.0029999999999999996"
, not "0.003"
, thanks to IEEE-754 double-precision binary floating point imprecision. (Whereas 0.09 / 3
is fine, gives us "0.03"
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With