【问题标题】:Select a random element out of an object/array based on the percent chance of it showing up [duplicate]根据出现的百分比从对象/数组中选择一个随机元素[重复]
【发布时间】:2021-08-15 13:46:56
【问题描述】:

我有一个看起来像这样的对象:

"rarity": {
    "Common": "60",
    "Uncommon": "25",
    "Rare": "10",
    ...
    "Legendary": "0.01",
    "Mythical": "0.001",
}

对象设置为“名称”:“% 机会”,因此普通有 60% 的机会出现,而神话有 100,000 分之一的机会出现 (0.001%)

我希望能够(快速)根据出现的百分比机会选择一个随机元素,但我对从哪里开始的想法为零。

如果这是错误的 StackExchange,请告诉我,它与编程相关,但很大程度上基于数学。

【问题讨论】:

  • 概率不加到 1,这是否意味着有时什么都不会被选择?还是概率是相对的?
  • 它们的和等于100吗?
  • @pilchard 这是一种缓慢且不太好的方法
  • @atanay 这并不能阻止它成为重复,只是表明应该在那里提供更好的答案。
  • 我应该把我的答案复制到那里吗?

标签: javascript


【解决方案1】:

让我们从 0 到 100000 之间的随机数开始:

const rnd = Math.random() * 100000;

现在,您需要将数字除以 1000,因为您要考虑 "Mythical": "0.001" 的逗号后最多 3 位小数

const percent = rnd / 1000;

最后,只需取包含percent 变量的数字的键。

示例

const rarity = {
    "Common": "60",
    "Uncommon": "25",
    "Rare": "10",
    "More rare": "4.989",  // to fill the rest of percentage
    "Legendary": "0.01",
    "Mythical": "0.001"
}

const rnd = Math.random() * 100000;

const percent = rnd / 1000;

let result = null, acc = 0;

Object.keys(rarity).forEach(key => {
  if (result === null && percent > 100 - rarity[key] - acc)
    result = key;
  acc += parseFloat(rarity[key]);
});

console.log(percent + " %", result);

在这里你可以测试每种可能性在 100000 循环中出现的次数

const rarity = {
    "Common": "60",
    "Uncommon": "25",
    "Rare": "10",
    "More rare": "4.989",  // to fill the rest of percentage
    "Legendary": "0.01",
    "Mythical": "0.001"
}


let testNumber = 100000;
const testResults = {};

while (0 < testNumber--) {

  const rnd = Math.random() * 100000;

  const percent = rnd / 1000;

  let result = null, acc = 0;

  Object.keys(rarity).forEach(key => {
    if (result === null && percent > 100 - rarity[key] - acc)
      result = key;
    acc += parseFloat(rarity[key]);
  });
  
  if (!testResults[result])
    testResults[result] = 0;
    
  testResults[result]++;

}

console.log(testResults);

【讨论】:

  • @atanay 你为什么这么说?
  • 对不起,我看错了。尽管如此,每个查询都是线性时间,效率低于对数。
【解决方案2】:

您可以通过以下方式实现:

  1. 根据每个元素的概率将 0 到 1 段划分为多个部分(例如,概率为 60% 的元素将占据该段的 60%)。
  2. 生成一个随机数并检查它位于哪个段。

第 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!');

希望这对您有所帮助。如果您需要任何澄清,请询问

注意: (在计算机科学中)列表/数组中值的下限是大于或等于它的最小元素。例如,array:[1,10,24,99] 和值 12。下限将是值为 24 的元素。 当数组从最小到最大排序时(就像我们的例子一样)找到每个值的下限可以通过二进制搜索非常快速地完成(O(log(n)))。

编辑:添加代码示例

【讨论】:

  • 你的方法的潜在问题是我没有粘贴整个对象。有十亿分之一的机会,所以我必须生成十亿个部分。这会是个问题吗?
  • 不,这个解决方案的重点是概率本身不会以任何方式影响效率。只有元素的数量会影响它。
猜你喜欢
  • 1970-01-01
  • 2016-02-26
  • 2019-02-26
  • 2012-01-23
  • 2013-03-03
  • 2014-07-23
  • 2016-09-10
相关资源
最近更新 更多