5

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.

SomePerson
  • 1,171
  • 4
  • 16
  • 45
  • For large sets u can u se random number and take your array elements to between numbers . Link above there is similar answer . – mr. pc_coder Apr 25 '20 at 08:27

2 Answers2

7

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());
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • It would be easier to define an array with weight per item and iterate with summing up until you exceed that sum. Defining probs like you did doesn't look very natural and in real life won't be easy. – Robo Robok Apr 25 '20 at 08:26
  • Also, how would you change the likelihood of an item without changing the proportions of other items in your case? You need to edit out all properties after that element. Not manageable. – Robo Robok Apr 25 '20 at 08:30
  • Now the second example is better. I would suggest removing that first one entirely. It's never done like that, unless there is some unchangeable, natural likelihood. – Robo Robok Apr 25 '20 at 08:32
  • The first one does require you to write the cumulative probabilities, but the logic it's using is probably a lot easier to understand at a glance – CertainPerformance Apr 25 '20 at 08:34
  • Many hard-to-work-with snippets look easy. – Robo Robok Apr 25 '20 at 08:36
1

Try in this way

var arr = ["emoji","emoji2","emoji3","emoji4","emoji5"]
var emoji = arr[Math.floor(Math.random() * (Math.random() < 0.75 ? arr.length - 1 : arr.length))]
dellink
  • 544
  • 3
  • 16