...

Source file src/github.com/growthbook/growthbook-golang/range.go

Documentation: github.com/growthbook/growthbook-golang

     1  package growthbook
     2  
     3  // Range represents a single bucket range.
     4  type Range struct {
     5  	Min float64
     6  	Max float64
     7  }
     8  
     9  func (r *Range) InRange(n float64) bool {
    10  	return n >= r.Min && n < r.Max
    11  }
    12  
    13  // This converts an experiment's coverage and variation weights into
    14  // an array of bucket ranges.
    15  func getBucketRanges(numVariations int, coverage float64, weights []float64) []Range {
    16  	// Make sure coverage is within bounds.
    17  	if coverage < 0 {
    18  		logWarn("Experiment coverage must be greater than or equal to 0")
    19  		coverage = 0
    20  	}
    21  	if coverage > 1 {
    22  		logWarn("Experiment coverage must be less than or equal to 1")
    23  		coverage = 1
    24  	}
    25  
    26  	// Default to equal weights if missing or invalid
    27  	if weights == nil || len(weights) == 0 {
    28  		weights = getEqualWeights(numVariations)
    29  	}
    30  	if len(weights) != numVariations {
    31  		logWarn("Experiment weights and variations arrays must be the same length")
    32  		weights = getEqualWeights(numVariations)
    33  	}
    34  
    35  	// If weights don't add up to 1 (or close to it), default to equal weights
    36  	totalWeight := 0.0
    37  	for i := range weights {
    38  		totalWeight += weights[i]
    39  	}
    40  	if totalWeight < 0.99 || totalWeight > 1.01 {
    41  		logWarn("Experiment weights must add up to 1")
    42  		weights = getEqualWeights(numVariations)
    43  	}
    44  
    45  	// Convert weights to ranges
    46  	cumulative := 0.0
    47  	ranges := make([]Range, len(weights))
    48  	for i := range weights {
    49  		start := cumulative
    50  		cumulative += weights[i]
    51  		ranges[i] = Range{start, start + coverage*weights[i]}
    52  	}
    53  	return ranges
    54  }
    55  
    56  // Given a hash and bucket ranges, assigns one of the bucket ranges.
    57  func chooseVariation(n float64, ranges []Range) int {
    58  	for i := range ranges {
    59  		if ranges[i].InRange(n) {
    60  			return i
    61  		}
    62  	}
    63  	return -1
    64  }
    65  
    66  func jsonRange(v interface{}, typeName string, fieldName string) (*Range, bool) {
    67  	vals, ok := jsonFloatArray(v, typeName, fieldName)
    68  	if !ok || vals == nil || len(vals) != 2 {
    69  		logError("Invalid JSON data type", typeName, fieldName)
    70  		return nil, false
    71  	}
    72  	return &Range{vals[0], vals[1]}, true
    73  }
    74  
    75  func jsonRangeArray(v interface{}, typeName string, fieldName string) ([]Range, bool) {
    76  	vals, ok := v.([]interface{})
    77  	if !ok {
    78  		logError("Invalid JSON data type", typeName, fieldName)
    79  		return nil, false
    80  	}
    81  	ranges := make([]Range, len(vals))
    82  	for i := range vals {
    83  		tmp, ok := jsonRange(vals[i], typeName, fieldName)
    84  		if !ok || tmp == nil {
    85  			return nil, false
    86  		}
    87  		ranges[i] = *tmp
    88  	}
    89  	return ranges, true
    90  }
    91  

View as plain text