Search code examples
javascriptprobability

How to increase or decrease the probability of an item of an array being picked?


So, let's say I'm making something like a slots machine, to use the emojis I'd like to use I'd define them in an array.

var arr = ["emoji","emoji2","emoji3","emoji4","emoji5"]

Let's say I'd want emojis 1 - 4 to appear more than 5, and say decrease the probability of emoji5 being picked.

I could do something large like:

var arr = [
"emoji","emoji2","emoji3","emoji4",
"emoji","emoji2","emoji3","emoji4",
"emoji","emoji2","emoji3","emoji4",
"emoji","emoji2","emoji3","emoji4",
"emoji","emoji2","emoji3","emoji4",
"emoji","emoji2","emoji3","emoji4","emoji5",
]
var emoji = arr[Math.floor(Math.random() * arr.length)]

But that is not a very efficient idea, so is it possible to do the idea above without making a very large array?

What I'm aiming for basically is to have an array like

var arr = ["emoji","emoji2","emoji3","emoji4","emoji5"]

and it would output something where emojis 1 - 4 would appear way more often than emoji5, without a large array.


Solution

  • For the general case of weighted probabilities, one option would be to have an object whose keys are of cumulative probabilities. Say you wanted emoji5 to occur 4% of the time - then, the cumulative probabilities would be 24, 48, 72, 96, 100 (where the last interval of 96 to 100 indicates emoji5's low weight) . Then generate a random number between 1-100 and find the first key which is greater than the picked number:

    const probs = {
      24: "emoji",
      48: "emoji2",
      72: "emoji3",
      96: "emoji4",
      100: "emoji5"
    };
    
    const keys = Object.keys(probs).map(Number);
    const generate = () => {
      const rand = Math.floor(Math.random() * 100);
      const key = keys.find(key => rand < key);
      return probs[key];
    };
    for (let i = 0; i < 10; i++) {
      console.log(generate());
    }

    Another option would be to associate a weight number with each string, and give the emoji5 a low one, add up the weights, generate a random number between 0 and the total weight, and find the first match:

    const weights = [
      [4, 'emoji'],
      [4, 'emoji2'],
      [4, 'emoji3'],
      [4, 'emoji4'],
      [1, 'emoji5'],
    ];
    
    const totalWeight = weights.reduce((a, [weight]) => a + weight, 0);
    const weightObj = {};
    let weightUsed = 0;
    for (const item of weights) {
      weightUsed += item[0];
      weightObj[weightUsed] = item;
    }
    const keys = Object.keys(weightObj);
    const generate = () => {
      const rand = Math.floor(Math.random() * totalWeight);
      const key = keys.find(key => rand < key);
      return weightObj[key][1];
    };
    for (let i = 0; i < 10; i++) {
      console.log(generate());
    }