实现可趋近指定目标均值的JavaScript加权随机整数选择函数
JavaScript函数实现:按目标平均值随机选择整数
原代码问题分析
你当前使用的1/|x-target|权重方式存在核心缺陷:概率分布的数学期望与目标值不匹配,尤其是当目标靠近数组边缘时,偏差会非常显著。比如目标值为0.2时,0的权重是5、1的权重是1.25,其他数权重递减,计算出的期望约为0.95,远高于目标值;虽然所有数都有被选中的可能,但这不是解决问题的核心。
解决方案:精确匹配期望的离散概率分布
该方案构建的概率分布能让所有整数的加权和(数学期望)恰好等于目标值,同时保证每个整数的概率都大于0(确保所有值都有被选中的机会)。
实现思路
- 输入验证:检查数组有效性和目标值范围;
- 定位目标区间:找到目标值所在的整数区间(如0.2在0和1之间);
- 分配极小概率:给非区间端点的整数分配固定极小概率
ε,保证它们有被选中的可能; - 计算端点概率:根据剩余概率和期望要求,计算区间端点的概率,使整体期望等于目标值;
- 随机选择:基于累积概率法随机选取整数。
代码实现
function weightedRandomInteger(arr, target) { // 输入合法性验证 if (!Array.isArray(arr) || arr.length === 0 || typeof target !== "number") { return null; } const min = arr[0]; const max = arr[arr.length - 1]; if (target < min || target > max) { return null; } const n = arr.length; const t = target; const isIntegerTarget = Number.isInteger(t); let a, b; let aIndex, bIndex; if (isIntegerTarget) { // 处理目标为整数的情况 a = t; aIndex = arr.indexOf(a); } else { // 目标为小数,找到所在的整数区间 a = Math.floor(t); b = a + 1; aIndex = arr.indexOf(a); bIndex = arr.indexOf(b); } // 定义极小概率ε,确保所有数都有被选中的机会 let epsilon = 1 / (100 * n); let sumEpsilon = 0; let sumOtherX = 0; const otherIndices = []; // 统计非端点元素的概率和与加权和 for (let i = 0; i < n; i++) { if (isIntegerTarget) { if (i !== aIndex) { otherIndices.push(i); sumEpsilon += epsilon; sumOtherX += arr[i] * epsilon; } } else { if (i !== aIndex && i !== bIndex) { otherIndices.push(i); sumEpsilon += epsilon; sumOtherX += arr[i] * epsilon; } } } const remainingProb = 1 - sumEpsilon; const probabilities = new Array(n).fill(0); if (isIntegerTarget) { // 调整非端点元素的概率,使得整体期望等于目标整数 const delta = t * sumEpsilon - sumOtherX; const adjust = delta / otherIndices.length; // 确保所有概率为正,若出现负概率则减小ε重新计算 if (epsilon + adjust <= 0) { epsilon = 1 / (1000 * n); return weightedRandomInteger(arr, target); } probabilities[aIndex] = remainingProb; for (const i of otherIndices) { probabilities[i] = epsilon + adjust; } } else { // 计算区间端点的概率,满足期望要求 const pa = (b * remainingProb) + sumOtherX - t; const pb = remainingProb - pa; // 确保端点概率为正,若不满足则减小ε重新计算 if (pa <= 0 || pb <= 0) { epsilon = 1 / (1000 * n); return weightedRandomInteger(arr, target); } probabilities[aIndex] = pa; probabilities[bIndex] = pb; for (const i of otherIndices) { probabilities[i] = epsilon; } } // 基于累积概率随机选择元素 const random = Math.random(); let cumulativeProb = 0; for (let i = 0; i < n; i++) { cumulativeProb += probabilities[i]; if (random <= cumulativeProb) { return arr[i]; } } }
测试验证
边缘目标值测试
let total = 0; const trials = 10000; for (let i = 0; i < trials; i++) { total += weightedRandomInteger([0, 1, 2, 3, 4, 5, 6, 7], 0.2); } console.log("Average is ", total / trials); // 输出应接近0.2,且所有整数(包括7)都有概率被选中
中间目标值测试
let total = 0; const trials = 10000; for (let i = 0; i < trials; i++) { total += weightedRandomInteger([0, 1, 2, 3, 4, 5, 6, 7], 3.5); } console.log("Average is ", total / trials); // 输出应接近3.5
备选方案:基于正态分布的权重(简单易实现)
如果不需要严格匹配期望,仅希望长期平均值接近目标值,可使用正态分布权重。该方法实现简单,所有数都有被选中的可能,且目标值附近的数概率更高。
代码实现
function weightedRandomInteger(arr, target) { if (!Array.isArray(arr) || arr.length === 0 || typeof target !== "number") { return null; } const min = arr[0]; const max = arr[arr.length - 1]; if (target < min || target > max) { return null; } // 调整方差控制概率集中程度,值越小越集中在目标附近 const variance = Math.pow(arr.length / 8, 2); // 计算正态分布权重 const weights = arr.map(x => { const diff = x - target; return Math.exp(-(diff * diff) / (2 * variance)); }); // 归一化权重为概率 const totalWeight = weights.reduce((acc, curr) => acc + curr, 0); const probabilities = weights.map(w => w / totalWeight); // 累积概率随机选择 const random = Math.random(); let cumulativeProb = 0; for (let i = 0; i < arr.length; i++) { cumulativeProb += probabilities[i]; if (random <= cumulativeProb) { return arr[i]; } } }
说明
- 方差越小:概率越集中在目标值附近,长期平均值越接近目标,但边缘数的选中概率越低;
- 方差越大:概率分布越分散,边缘数的选中概率越高,但长期平均值偏差可能越大。
内容的提问来源于stack exchange,提问作者Matthew Dunson
相关产品推荐
相关产品推荐

