getWeightedRandomFromSeed.test.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // 分布概率验证测试
  2. // 测试 getWeightedRandomFromSeed 函数的分布概率
  3. // 复制核心函数实现
  4. function seedRandom(str) {
  5. let hash = 0
  6. for (let i = 0; i < str.length; i++) {
  7. const char = str.charCodeAt(i)
  8. hash = ((hash << 5) - hash) + char
  9. hash = hash & hash
  10. }
  11. return Math.abs(hash)
  12. }
  13. function getWeightedRandomFromSeed(array, weights, seed, offset = 0) {
  14. // 使用种子生成 0-1 之间的伪随机数
  15. const pseudoRandom = ((seed + offset) * 9301 + 49297) % 233280 / 233280.0
  16. // 累计权重
  17. let cumulativeWeight = 0
  18. const cumulativeWeights = weights.map(weight => cumulativeWeight += weight)
  19. const totalWeight = cumulativeWeights[cumulativeWeights.length - 1]
  20. // 找到对应的索引
  21. const randomValue = pseudoRandom * totalWeight
  22. const index = cumulativeWeights.findIndex(weight => randomValue <= weight)
  23. return array[index >= 0 ? index : 0]
  24. }
  25. // 测试配置
  26. const levels = [
  27. { name: '种', emoji: '🌱' },
  28. { name: '芽', emoji: '🍃' },
  29. { name: '叶', emoji: '🌿' },
  30. { name: '蕾', emoji: '🌸' },
  31. { name: '花', emoji: '🌺' },
  32. { name: '宝石权杖', emoji: '💎' }
  33. ]
  34. const levelProbabilities = [0.1, 0.2, 0.3, 0.3, 0.07, 0.03]
  35. // 生成大量测试样本
  36. function runDistributionTest(sampleSize = 100000) {
  37. console.log(`\n===== 分布概率验证测试 (样本数: ${sampleSize}) =====`)
  38. console.log('预期概率:', levelProbabilities.map((p, i) => `${levels[i].name}: ${(p * 100).toFixed(1)}%`).join(', '))
  39. const results = {}
  40. levels.forEach(level => results[level.name] = 0)
  41. // 生成测试样本
  42. for (let i = 0; i < sampleSize; i++) {
  43. // 使用不同的种子和偏移量来模拟真实使用场景
  44. const testSeed = seedRandom(`test_${i}`)
  45. const level = getWeightedRandomFromSeed(levels, levelProbabilities, testSeed, 6)
  46. results[level.name]++
  47. }
  48. // 计算实际概率
  49. console.log('\n实际分布:')
  50. levels.forEach((level, index) => {
  51. const actualCount = results[level.name]
  52. const actualProbability = actualCount / sampleSize
  53. const expectedProbability = levelProbabilities[index]
  54. const deviation = Math.abs(actualProbability - expectedProbability)
  55. const deviationPercent = (deviation / expectedProbability * 100).toFixed(2)
  56. console.log(`${level.emoji} ${level.name}: ${actualCount}次 (${(actualProbability * 100).toFixed(2)}%) | 偏差: ${deviationPercent}%`)
  57. })
  58. // 计算卡方检验
  59. let chiSquare = 0
  60. levels.forEach((level, index) => {
  61. const observed = results[level.name]
  62. const expected = sampleSize * levelProbabilities[index]
  63. chiSquare += Math.pow(observed - expected, 2) / expected
  64. })
  65. console.log(`\n卡方值: ${chiSquare.toFixed(4)}`)
  66. console.log(`自由度: ${levels.length - 1}`)
  67. // 简单的分布质量评估
  68. const maxExpectedDeviation = 0.02 // 2% 的最大预期偏差
  69. let isDistributionGood = true
  70. levels.forEach((level, index) => {
  71. const actualProbability = results[level.name] / sampleSize
  72. const expectedProbability = levelProbabilities[index]
  73. const deviation = Math.abs(actualProbability - expectedProbability)
  74. if (deviation > maxExpectedDeviation) {
  75. isDistributionGood = false
  76. }
  77. })
  78. console.log(`\n分布质量评估: ${isDistributionGood ? '✅ 良好' : '⚠️ 需要关注'}`)
  79. return {
  80. results,
  81. chiSquare,
  82. isDistributionGood
  83. }
  84. }
  85. // 测试种子确定性
  86. function testSeedDeterminism() {
  87. console.log('\n===== 种子确定性测试 =====')
  88. const testSeeds = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
  89. testSeeds.forEach(name => {
  90. const seed = seedRandom(name)
  91. const results = []
  92. // 同一个种子应该产生相同的结果
  93. for (let i = 0; i < 5; i++) {
  94. const level = getWeightedRandomFromSeed(levels, levelProbabilities, seed, 6)
  95. results.push(level.name)
  96. }
  97. const allSame = results.every(result => result === results[0])
  98. console.log(`${name} (种子: ${seed}): ${results[0]} ${allSame ? '✅' : '❌'}`)
  99. })
  100. }
  101. // 测试边界情况
  102. function testEdgeCases() {
  103. console.log('\n===== 边界情况测试 =====')
  104. // 测试极端权重
  105. const extremeWeights = [0, 0, 0, 0, 0, 1] // 只有最后一个有权重
  106. let result = getWeightedRandomFromSeed(levels, extremeWeights, 12345, 6)
  107. console.log(`极端权重测试: ${result.name} (应该是宝石权杖)`)
  108. // 测试相等权重
  109. const equalWeights = [1, 1, 1, 1, 1, 1]
  110. const equalResults = {}
  111. levels.forEach(level => equalResults[level.name] = 0)
  112. for (let i = 0; i < 10000; i++) {
  113. result = getWeightedRandomFromSeed(levels, equalWeights, i, 6)
  114. equalResults[result.name]++
  115. }
  116. console.log('相等权重分布:')
  117. levels.forEach(level => {
  118. const count = equalResults[level.name]
  119. const percentage = (count / 10000 * 100).toFixed(1)
  120. console.log(` ${level.name}: ${percentage}% (期望: ~16.7%)`)
  121. })
  122. }
  123. // 运行所有测试
  124. function runAllTests() {
  125. console.log('开始运行 getWeightedRandomFromSeed 分布概率验证测试...')
  126. // 小样本测试
  127. runDistributionTest(10000)
  128. // 大样本测试
  129. runDistributionTest(100000)
  130. // 种子确定性测试
  131. testSeedDeterminism()
  132. // 边界情况测试
  133. testEdgeCases()
  134. console.log('\n===== 测试完成 =====')
  135. }
  136. // 如果直接运行此文件
  137. if (typeof module !== 'undefined' && require.main === module) {
  138. runAllTests()
  139. }
  140. // 导出函数供其他测试使用
  141. if (typeof module !== 'undefined') {
  142. module.exports = {
  143. runDistributionTest,
  144. testSeedDeterminism,
  145. testEdgeCases,
  146. runAllTests
  147. }
  148. }