ScooterMcGee
ScooterMcGee

Reputation: 47

How to Influence Math.Random to Make Numbers More Likely?

I am trying to figure out how to influence Math.random() so that items where the counter of timesSinceLastSelected with a higher number have a higher chance of being selected.

For example, if "item1" has a timesSinceLastSelected of 4 and "item2" has a timesSinceLastSelected of 3, then "item1" might have a 75% to be chosen, but "item2" still has a 25% chance even if you don't know how many items are in the array.

let arr = [{ //Unknown number of items
    "name": "item1",
    "deselected": false,
    "favourite": false,
    "timesSinceLastSelected": 0
  },
  {
    "name": "item2",
    "deselected": false,
    "favourite": true,
    "timesSinceLastSelected": 0
  },
  {
    "name": "item3",
    "deselected": true,
    "favourite": false,
    "timesSinceLastSelected": 0
  },
  {
    "name": "item4",
    "deselected": true,
    "favourite": true,
    "timesSinceLastSelected": 0
  }
];

$('#button').click(function() {
  // Selecting only the items where deselected = false
  let filter_deselected = arr.filter(val => val.deselected === false);
  // Selecting only the items where favourite = true
  let filter_favourite = filter_deselected.filter(val => val.favourite === true);

  // How do I make timesSinceLastSelected with higher numbers more likely?

  if (Math.round(Math.random() * 10 > 5)) { // Selecte random item
    var selected = Math.floor(Math.random() * filter_deselected.length);
  } else { // Select random favourite item
    var selected = Math.floor(Math.random() * filter_favourite.length);
  }


  // Adding 1 to every item's counter
  arr.forEach(function(val) {
    val.timesSinceLastSelected++;
  });
  // Returning the selected item's counter back to 0
  filter_deselected[selected].timesSinceLastSelected = 0;
  // Logging the randomly selected item
  console.log(filter_deselected[selected]);
});

https://jsfiddle.net/knsfr2xh/

Upvotes: 0

Views: 96

Answers (2)

GirkovArpa
GirkovArpa

Reputation: 4912

const getSum = (data, key) => data.reduce((sum, { [key]: n }) => sum += (n + 1), 0);

const randomThreshold = (data, key) => ~~(Math.random() * getSum(data, key));

const randomElement = (data, key, threshold = null, total = 0, i = 0) => {
  const datum = data[i];
  const newTotal = total + datum[key] + 1;
  const newThreshold = threshold || randomThreshold(data, key);
  return newTotal >= newThreshold ? datum : randomElement(data, key, newThreshold, newTotal, i + 1);
}

Use like this:

console.log(randomElement(items, 'timesSinceLastSelected').name)

const items = [
  {
    "name": "item1",
    "deselected": false,
    "favourite": false,
    "timesSinceLastSelected": 4
  },
  {
    "name": "item2",
    "deselected": false,
    "favourite": true,
    "timesSinceLastSelected": 3
  },
  {
    "name": "item3",
    "deselected": true,
    "favourite": false,
    "timesSinceLastSelected": 2
  },
  {
    "name": "item4",
    "deselected": true,
    "favourite": true,
    "timesSinceLastSelected": 1
  }
];

const getSum = (data, key) => data.reduce((sum, { [key]: n }) => sum += (n + 1), 0);

const randomThreshold = (data, key) => ~~(Math.random() * getSum(data, key));

const randomElement = (data, key, threshold = null, total = 0, i = 0) => {
  const datum = data[i];
  const newTotal = total + datum[key] + 1;
  const newThreshold = threshold || randomThreshold(data, key);
  return newTotal >= newThreshold ? datum : randomElement(data, key, newThreshold, newTotal, i + 1);
}

for (let i = 0; i < 10; i++) {
  console.log(randomElement(items, 'timesSinceLastSelected').name);
}

Based on this article.

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386600

You could get the sum of the not selected count and get a random number up to the sum and get the item.

Then update unselected and reset the selected item.

At start give all items one for equal distribution.

const
    getRandom = () => {
        let
            sum = items.reduce((s, { timesNotSelected }) => s + timesNotSelected, 0),
            random = Math.random() * sum,
            item = items.find(({ timesNotSelected }) => (random -= timesNotSelected) <= 0);
            
        items.forEach(o => o.timesNotSelected = o === item ? 1 : o.timesNotSelected + 1)
        return item;
    },
    items = [{ id: 1, timesNotSelected: 1 }, { id: 2, timesNotSelected: 1 }, { id: 3, timesNotSelected: 1 }, { id: 4, timesNotSelected: 1 }];    

console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
console.log(getRandom().id);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Related Questions