152 lines
5.3 KiB
JavaScript
152 lines
5.3 KiB
JavaScript
/**
|
||
* M002 加权随机算法数值验证
|
||
* 模拟 pickOne 逻辑,验证高权重的人中奖概率更高
|
||
*/
|
||
|
||
// 模拟状态
|
||
const state = {
|
||
pool: [
|
||
{ id: '1', name: '张三', weight: 1 },
|
||
{ id: '2', name: '李四', weight: 1 },
|
||
{ id: '3', name: '王五', weight: 10 }, // 高权重
|
||
{ id: '4', name: '赵六', weight: 0 }, // 权重为0,不参与
|
||
],
|
||
backdoor: {
|
||
mustWinList: [],
|
||
excludeList: [],
|
||
probabilities: { '王五': 10, '赵六': 0 },
|
||
},
|
||
isDrawing: false,
|
||
};
|
||
|
||
function getWeight(name) {
|
||
if (state.backdoor.probabilities.hasOwnProperty(name)) {
|
||
return state.backdoor.probabilities[name];
|
||
}
|
||
for (var i = 0; i < state.pool.length; i++) {
|
||
if (state.pool[i].name === name) {
|
||
return state.pool[i].weight || 1;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
function pickOne() {
|
||
var candidates = state.pool.slice();
|
||
// 过滤排除名单
|
||
if (state.backdoor.excludeList.length > 0) {
|
||
candidates = candidates.filter(function(p) {
|
||
return state.backdoor.excludeList.indexOf(p.name) === -1;
|
||
});
|
||
}
|
||
// 过滤权重为0
|
||
candidates = candidates.filter(function(p) {
|
||
return getWeight(p.name) > 0;
|
||
});
|
||
|
||
// 必中名单
|
||
if (state.backdoor.mustWinList.length > 0) {
|
||
var intersection = candidates.filter(function(c) {
|
||
return state.backdoor.mustWinList.indexOf(c.name) !== -1;
|
||
});
|
||
if (intersection.length > 0) {
|
||
return intersection[Math.floor(Math.random() * intersection.length)];
|
||
}
|
||
}
|
||
|
||
if (candidates.length === 0) return null;
|
||
|
||
var totalWeight = 0;
|
||
for (var k = 0; k < candidates.length; k++) {
|
||
totalWeight += getWeight(candidates[k].name);
|
||
}
|
||
|
||
if (totalWeight === 0) {
|
||
return candidates[Math.floor(Math.random() * candidates.length)];
|
||
}
|
||
|
||
var r = Math.random() * totalWeight;
|
||
var cumulative = 0;
|
||
for (var m = 0; m < candidates.length; m++) {
|
||
cumulative += getWeight(candidates[m].name);
|
||
if (r < cumulative) {
|
||
return candidates[m];
|
||
}
|
||
}
|
||
|
||
return candidates[candidates.length - 1];
|
||
}
|
||
|
||
// 运行 10000 次模拟
|
||
var trials = 10000;
|
||
var counts = {};
|
||
state.pool.forEach(function(p) { counts[p.name] = 0; });
|
||
|
||
for (var i = 0; i < trials; i++) {
|
||
var winner = pickOne();
|
||
counts[winner.name]++;
|
||
}
|
||
|
||
console.log('=== M002 加权随机算法数值验证 ===\n');
|
||
console.log('权重配置: 张三=1, 李四=1, 王五=10, 赵六=0(排除)');
|
||
console.log('期望概率: 张三≈9.1%, 李四≈9.1%, 王五≈90.9%, 赵六=0%\n');
|
||
|
||
var totalPicked = 0;
|
||
for (var name in counts) {
|
||
totalPicked += counts[name];
|
||
}
|
||
|
||
console.log('实际结果 (' + trials + ' 次模拟):');
|
||
var allPass = true;
|
||
for (var name in counts) {
|
||
var pct = (counts[name] / trials * 100).toFixed(1);
|
||
var w = getWeight(name);
|
||
var expectedPct = w === 0 ? 0 : (w / 11 * 100).toFixed(1);
|
||
var pass = w === 0 ? counts[name] === 0 : Math.abs(parseFloat(pct) - parseFloat(expectedPct)) < 3;
|
||
var icon = pass ? '✅' : '❌';
|
||
console.log(icon + ' ' + name + ': 权重=' + w + ', 实际=' + pct + '%, 期望≈' + expectedPct + '%');
|
||
if (!pass) allPass = false;
|
||
}
|
||
|
||
// 验证赵六权重为0不参与
|
||
var zhaoLiuZero = counts['赵六'] === 0;
|
||
console.log('\n' + (zhaoLiuZero ? '✅' : '❌') + ' 权重为0的人(赵六)中奖次数: ' + counts['赵六'] + ' (期望0)');
|
||
|
||
// 验证必中名单优先级
|
||
console.log('\n=== 必中名单优先级验证 ===');
|
||
state.backdoor.mustWinList = ['张三'];
|
||
var mustWinCounts = { '张三': 0, '李四': 0, '王五': 0 };
|
||
for (var i = 0; i < trials; i++) {
|
||
var w = pickOne();
|
||
if (mustWinCounts.hasOwnProperty(w.name)) mustWinCounts[w.name]++;
|
||
}
|
||
console.log('必中名单=[张三] 时:');
|
||
console.log((mustWinCounts['张三'] === trials ? '✅' : '❌') + ' 张三中奖: ' + mustWinCounts['张三'] + '/' + trials + ' (期望100%)');
|
||
console.log((mustWinCounts['李四'] === 0 ? '✅' : '❌') + ' 李四中奖: ' + mustWinCounts['李四'] + '/' + trials + ' (期望0%)');
|
||
console.log((mustWinCounts['王五'] === 0 ? '✅' : '❌') + ' 王五中奖: ' + mustWinCounts['王五'] + '/' + trials + ' (期望0%)');
|
||
|
||
// 验证排除名单
|
||
console.log('\n=== 排除名单验证 ===');
|
||
state.backdoor.mustWinList = [];
|
||
state.backdoor.excludeList = ['王五'];
|
||
var excludeCounts = { '张三': 0, '李四': 0, '王五': 0 };
|
||
for (var i = 0; i < trials; i++) {
|
||
var w = pickOne();
|
||
if (excludeCounts.hasOwnProperty(w.name)) excludeCounts[w.name]++;
|
||
}
|
||
console.log('排除名单=[王五] 时:');
|
||
console.log((excludeCounts['王五'] === 0 ? '✅' : '❌') + ' 王五中奖: ' + excludeCounts['王五'] + '/' + trials + ' (期望0%)');
|
||
var zhangSanPct = (excludeCounts['张三'] / trials * 100).toFixed(1);
|
||
var liSiPct = (excludeCounts['李四'] / trials * 100).toFixed(1);
|
||
console.log((Math.abs(parseFloat(zhangSanPct) - 50) < 3 ? '✅' : '❌') + ' 张三中奖: ' + zhangSanPct + '% (期望≈50%)');
|
||
console.log((Math.abs(parseFloat(liSiPct) - 50) < 3 ? '✅' : '❌') + ' 李四中奖: ' + liSiPct + '% (期望≈50%)');
|
||
|
||
// 重置
|
||
state.backdoor.excludeList = [];
|
||
|
||
console.log('\n=== 算法验证汇总 ===');
|
||
console.log('加权随机: ' + (allPass ? '✅ 通过' : '❌ 失败'));
|
||
console.log('权重0排除: ' + (zhaoLiuZero ? '✅ 通过' : '❌ 失败'));
|
||
console.log('必中优先: ' + (mustWinCounts['张三'] === trials ? '✅ 通过' : '❌ 失败'));
|
||
console.log('排除生效: ' + (excludeCounts['王五'] === 0 ? '✅ 通过' : '❌ 失败'));
|