Repository navigation
Expand file tree
/
Copy pathAlgorithm.cs
More file actions
134 lines (123 loc) · 5.79 KB
/
Copy pathAlgorithm.cs
File metadata and controls
134 lines (123 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
namespace SecRandom.NewAlgorithm;
public static class FairDrawWeights
{
public static WeightResult Compute(
IReadOnlyList<StudentMetaData> pool,
IReadOnlyList<DrawHistory> histories,
WeightSettings settings,
int batchSize = 1
)
{
ArgumentNullException.ThrowIfNull(pool);
ArgumentNullException.ThrowIfNull(histories);
ArgumentNullException.ThrowIfNull(settings);
ArgumentOutOfRangeException.ThrowIfLessThan(batchSize, 1);
if (pool.Count == 0)
throw new ArgumentException("候选池为空; 周期已抽干, 应由调用方重置计数", nameof(pool));
if (batchSize > pool.Count)
throw new ArgumentOutOfRangeException(nameof(batchSize),
$"批量 {batchSize} 超过池内人数 {pool.Count}; 上限可能已把池子抽小");
var poolSize = pool.Count;
var drawCountById = histories.ToDictionary(record => record.Id, record => (double)record.DrawCount);
var drawCount = new double[poolSize];
var multiplierSum = 0.0;
var share = new double[poolSize];
for (var index = 0; index < poolSize; index++)
{
var student = pool[index];
if (student.Multiplier <= 0.0 || double.IsNaN(student.Multiplier) || double.IsInfinity(student.Multiplier))
throw new ArgumentException($"学生 {student.Id} 的倍率 {student.Multiplier} 必须为正的有限值", nameof(pool));
var count = drawCountById.GetValueOrDefault(student.Id, 0.0);
if (count < 0.0)
throw new ArgumentException($"学生 {student.Id} 的次数为负", nameof(histories));
drawCount[index] = count;
multiplierSum += student.Multiplier;
}
var totalDraws = 0.0;
for (var index = 0; index < poolSize; index++)
{
share[index] = pool[index].Multiplier / multiplierSum;
totalDraws += drawCount[index];
}
var personalHorizon = settings.PersonalHorizonRounds * poolSize;
var personalDebt = new double[poolSize];
var weight = new double[poolSize];
for (var index = 0; index < poolSize; index++)
{
personalDebt[index] = Math.Max(
share[index] * (totalDraws + personalHorizon) - drawCount[index], 0.0
);
weight[index] = personalDebt[index];
}
var dimensionCount = settings.Dimensions.Length;
var dimensionDebt = new double[poolSize][];
for (var index = 0; index < poolSize; index++)
dimensionDebt[index] = new double[dimensionCount];
for (var slot = 0; slot < dimensionCount; slot++)
{
var dimension = settings.Dimensions[slot];
if (dimension.Dimension < 0)
throw new ArgumentException("维度下标不能为负", nameof(settings));
var labelCount = 0;
for (var index = 0; index < poolSize; index++)
{
var labels = pool[index].Labels;
if (dimension.Dimension >= labels.Length)
throw new ArgumentException(
$"学生 {pool[index].Id} 缺少维度 {dimension.Dimension} 的标签", nameof(pool));
var label = labels[dimension.Dimension];
if (label < 0)
throw new ArgumentException($"学生 {pool[index].Id} 的标签为负", nameof(pool));
labelCount = Math.Max(labelCount, label + 1);
}
var labelShare = new double[labelCount];
var labelDrawn = new double[labelCount];
for (var index = 0; index < poolSize; index++)
{
var label = pool[index].Labels[dimension.Dimension];
labelShare[label] += share[index];
labelDrawn[label] += drawCount[index];
}
var horizon = dimension.HorizonPerPick * batchSize;
var debt = new double[labelCount];
for (var label = 0; label < labelCount; label++)
debt[label] = Math.Max(labelShare[label] * (totalDraws + horizon) - labelDrawn[label], 0.0);
for (var index = 0; index < poolSize; index++)
{
var value = debt[pool[index].Labels[dimension.Dimension]];
dimensionDebt[index][slot] = value;
weight[index] *= value;
}
}
var weightSum = weight.Sum();
var degraded = weightSum <= 0.0;
if (degraded)
{
Array.Fill(weight, 1.0);
weightSum = poolSize;
}
var candidates = new CandidateWeight[poolSize];
for (var index = 0; index < poolSize; index++)
candidates[index] = new CandidateWeight(
pool[index].Id,
weight[index],
personalDebt[index],
dimensionDebt[index]);
return new WeightResult(candidates, weightSum, degraded, poolSize == 1);
}
public static double[] ToProbabilities(WeightResult result, WeightSettings settings)
{
ArgumentNullException.ThrowIfNull(result);
ArgumentNullException.ThrowIfNull(settings);
var floor = settings.RandomFloor;
if (floor is < 0.0 or > 1.0)
throw new ArgumentOutOfRangeException(nameof(settings), "RandomFloor 须在 [0, 1] 内");
var poolSize = result.Candidates.Count;
var floorShare = floor / poolSize;
var probabilities = new double[poolSize];
for (var index = 0; index < poolSize; index++)
probabilities[index] =
(1.0 - floor) * result.Candidates[index].Weight / result.WeightSum + floorShare;
return probabilities;
}
}