chore: v1.1.0 - 5项功能优化
This commit is contained in:
151
Test/test-weighted-random.js
Normal file
151
Test/test-weighted-random.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 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 ? '✅ 通过' : '❌ 失败'));
|
||||
Reference in New Issue
Block a user