-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathrange.go
90 lines (81 loc) · 2.33 KB
/
range.go
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
package growthbook
// Range represents a single bucket range.
type Range struct {
Min float64
Max float64
}
func (r *Range) InRange(n float64) bool {
return n >= r.Min && n < r.Max
}
// This converts an experiment's coverage and variation weights into
// an array of bucket ranges.
func getBucketRanges(numVariations int, coverage float64, weights []float64) []Range {
// Make sure coverage is within bounds.
if coverage < 0 {
logWarn("Experiment coverage must be greater than or equal to 0")
coverage = 0
}
if coverage > 1 {
logWarn("Experiment coverage must be less than or equal to 1")
coverage = 1
}
// Default to equal weights if missing or invalid
if weights == nil || len(weights) == 0 {
weights = getEqualWeights(numVariations)
}
if len(weights) != numVariations {
logWarn("Experiment weights and variations arrays must be the same length")
weights = getEqualWeights(numVariations)
}
// If weights don't add up to 1 (or close to it), default to equal weights
totalWeight := 0.0
for i := range weights {
totalWeight += weights[i]
}
if totalWeight < 0.99 || totalWeight > 1.01 {
logWarn("Experiment weights must add up to 1")
weights = getEqualWeights(numVariations)
}
// Convert weights to ranges
cumulative := 0.0
ranges := make([]Range, len(weights))
for i := range weights {
start := cumulative
cumulative += weights[i]
ranges[i] = Range{start, start + coverage*weights[i]}
}
return ranges
}
// Given a hash and bucket ranges, assigns one of the bucket ranges.
func chooseVariation(n float64, ranges []Range) int {
for i := range ranges {
if ranges[i].InRange(n) {
return i
}
}
return -1
}
func jsonRange(v interface{}, typeName string, fieldName string) (*Range, bool) {
vals, ok := jsonFloatArray(v, typeName, fieldName)
if !ok || vals == nil || len(vals) != 2 {
logError("Invalid JSON data type", typeName, fieldName)
return nil, false
}
return &Range{vals[0], vals[1]}, true
}
func jsonRangeArray(v interface{}, typeName string, fieldName string) ([]Range, bool) {
vals, ok := v.([]interface{})
if !ok {
logError("Invalid JSON data type", typeName, fieldName)
return nil, false
}
ranges := make([]Range, len(vals))
for i := range vals {
tmp, ok := jsonRange(vals[i], typeName, fieldName)
if !ok || tmp == nil {
return nil, false
}
ranges[i] = *tmp
}
return ranges, true
}