Reputation: 47
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
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
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