[英]How to choose a weighted random array element in Javascript?
例如:数组中有四个项目。 我想随机得到一个,像这样:
array items = [
"bike" //40% chance to select
"car" //30% chance to select
"boat" //15% chance to select
"train" //10% chance to select
"plane" //5% chance to select
]
上面的两个答案都依赖于会很快变慢的方法,尤其是被接受的方法。
function weighted_random(items, weights) {
var i;
for (i = 0; i < weights.length; i++)
weights[i] += weights[i - 1] || 0;
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return items[i];
}
截至 2020 年 12 月,我用这个解决方案替换了我的旧 ES6 解决方案,因为旧浏览器不支持 ES6,我个人认为这个解决方案更具可读性。
如果您更愿意使用具有item
和weight
属性的对象:
function weighted_random(options) {
var i;
var weights = [];
for (i = 0; i < options.length; i++)
weights[i] = options[i].weight + (weights[i - 1] || 0);
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return options[i].item;
}
解释:
我制作了这张图表,展示了它是如何工作的:
该图显示了当给出权重为[5, 2, 8, 3]
的输入时会发生什么。 通过对权重进行部分求和,你只需要找到第一个和随机数一样大的,那就是随机选择的项目。
如果在两个权重的边界上选择一个随机数,例如图中的7
和15
,我们选择较长的一个。 这是因为Math.random
可以选择0
而不能选择1
,所以我们得到了一个公平的分布。 如果我们选择较短的, A
可以在 18 次4
选择3
2
( 0
1
,从而赋予它比应有的更高的权重。
一些带有通配符处理的 es6 方法:
const randomizer = (values) => {
let i, pickedValue,
randomNr = Math.random(),
threshold = 0;
for (i = 0; i < values.length; i++) {
if (values[i].probability === '*') {
continue;
}
threshold += values[i].probability;
if (threshold > randomNr) {
pickedValue = values[i].value;
break;
}
if (!pickedValue) {
//nothing found based on probability value, so pick element marked with wildcard
pickedValue = values.filter((value) => value.probability === '*');
}
}
return pickedValue;
}
示例用法:
let testValues = [{
value : 'aaa',
probability: 0.1
},
{
value : 'bbb',
probability: 0.3
},
{
value : 'ccc',
probability: '*'
}]
randomizer(testValues); // will return "aaa" in 10% calls,
//"bbb" in 30% calls, and "ccc" in 60% calls;
这是一种比其他答案建议的更快的方法......
您可以通过以下方式实现您想要的:
步骤1
为概率数组制作一个前缀和数组,其中的每个值将表示其相应部分的结束位置。
例如:如果我们有概率:60% (0.6)、30%、5%、3%、2%。 前缀和数组将是: [0.6,0.9,0.95,0.98,1]
所以我们将有一个这样划分的段(大约): [ | | ||]
[ | | ||]
第2步
生成一个介于 0 和 1 之间的随机数,并在前缀和数组中找到它的下限。 您将找到的索引是随机数所在的段的索引
以下是实现此方法的方法:
let obj = {
"Common": "60",
"Uncommon": "25",
"Rare": "10",
"Legendary": "0.01",
"Mythical": "0.001"
}
// turning object into array and creating the prefix sum array:
let sums = [0]; // prefix sums;
let keys = [];
for(let key in obj) {
keys.push(key);
sums.push(sums[sums.length-1] + parseFloat(obj[key])/100);
}
sums.push(1);
keys.push('NONE');
// Step 2:
function lowerBound(target, low = 0, high = sums.length - 1) {
if (low == high) {
return low;
}
const midPoint = Math.floor((low + high) / 2);
if (target < sums[midPoint]) {
return lowerBound(target, low, midPoint);
} else if (target > sums[midPoint]) {
return lowerBound(target, midPoint + 1, high);
} else {
return midPoint + 1;
}
}
function getRandom() {
return lowerBound(Math.random());
}
console.log(keys[getRandom()], 'was picked!');
希望你觉得这很有帮助。 注意:(在计算机科学中)列表/数组中值的下限是大于或等于它的最小元素。 例如,数组: [1,10,24,99]
和值 12。下限将是值为 24 的元素。当数组从最小到最大排序时(就像在我们的例子中)找到每个的下限使用二进制搜索(O(log(n)))可以非常快速地完成值。
这是一个 O(1) (常数时间)算法来解决你的问题。
生成一个从 0 到 99 的随机数(总共 100 个)。 如果在给定的子范围内有 40 个数字(0 到 39),那么随机选择的数字有 40% 的概率落在这个范围内。 请参阅下面的代码。
const number = Math.floor(Math.random() * 99); // 0 to 99
let element;
if (number >= 0 && number <= 39) {
// 40% chance that this code runs. Hence, it is a bike.
element = "bike";
}
else if (number >= 40 && number <= 69) {
// 30% chance that this code runs. Hence, it is a car.
element = "car";
}
else if (number >= 70 && number <= 84) {
// 15% chance that this code runs. Hence, it is a boat.
element = "boat";
}
else if (number >= 85 && number <= 94) {
// 10% chance that this code runs. Hence, it is a train.
element = "train";
}
else if (number >= 95 && number <= 99) {
// 5% chance that this code runs. Hence, it is a plane.
element = "plane";
}
还记得这个小学的数学原理吗? “指定分布中的所有数字被随机选择的概率均等。”
这告诉我们每个随机数在特定范围内出现的概率均等,无论该范围有多大或多小。
就是这样。 这应该工作!
我将我的解决方案添加为一种适用于较小数组(无缓存)的方法:
static weight_random(arr, weight_field){
if(arr == null || arr === undefined){
return null;
}
const totals = [];
let total = 0;
for(let i=0;i<arr.length;i++){
total += arr[i][weight_field];
totals.push(total);
}
const rnd = Math.floor(Math.random() * total);
let selected = arr[0];
for(let i=0;i<totals.length;i++){
if(totals[i] > rnd){
selected = arr[i];
break;
}
}
return selected;
}
像这样运行它(提供数组和权重属性):
const wait_items = [
{"w" : 20, "min_ms" : "5000", "max_ms" : "10000"},
{"w" : 20, "min_ms" : "10000", "max_ms" : "20000"},
{"w" : 20, "min_ms" : "40000", "max_ms" : "80000"}
]
const item = weight_random(wait_items, "w");
console.log(item);
ES2015 版Radvylf Programs 的回答
function getWeightedRandomItem(items) {
const weights = items.reduce((acc, item, i) => {
acc.push(item.weight + (acc[i - 1] || 0));
return acc;
}, []);
const random = Math.random() * weights[weights.length - 1];
return items[weights.findIndex((weight) => weight > random)];
}
和 ES2022
function getWeightedRandomItem(items) {
const weights = items.reduce((acc, item, i) => {
acc.push(item.weight + (acc[i - 1] ?? 0));
return acc;
}, []);
const random = Math.random() * weights.at(-1);
return items[weights.findIndex((weight) => weight > random)];
}
你当然可以。 这是一个简单的代码:
// Object or Array. Which every you prefer.
var item = {
bike:40, // Weighted Probability
care:30, // Weighted Probability
boat:15, // Weighted Probability
train:10, // Weighted Probability
plane:5 // Weighted Probability
// The number is not really percentage. You could put whatever number you want.
// Any number less than 1 will never occur
};
function get(input) {
var array = []; // Just Checking...
for(var item in input) {
if ( input.hasOwnProperty(item) ) { // Safety
for( var i=0; i<input[item]; i++ ) {
array.push(item);
}
}
}
// Probability Fun
return array[Math.floor(Math.random() * array.length)];
}
console.log(get(item)); // See Console.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.