a_b_split 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/a_b_split/configuration.rb +1 -1
- data/lib/a_b_split/functions/heaviside_weighted_split.rb +49 -0
- data/lib/a_b_split/functions/md5_weighted_split.rb +40 -0
- data/lib/a_b_split/functions/weighted_split.rb +44 -39
- data/lib/a_b_split/test.rb +33 -3
- data/lib/a_b_split.rb +2 -2
- metadata +5 -4
- data/lib/a_b_split/functions/sigmoid.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d6337e9baf884dc54218a8b6ad030b7256ec30c
|
4
|
+
data.tar.gz: e4af93072dfacd1a96f4d73a0182bc0c8a7b0104
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9e09bbc215026262b7bc48919e99ad3092653967870daedeef421b49453cb4cd0223777dba430d0d4f83243a41711fe3accc4ab3368458bf102c7c3fb9abec1
|
7
|
+
data.tar.gz: d21be140d7517554a89346951de8a2141daafa6dcd2b5a93164fd46058f54c73e34e6a467be601307d42fc7f3632d6f776d5f01f1c83ff6fb5adbae8c650eea4
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require_relative 'weighted_split'
|
3
|
+
|
4
|
+
module ABSplit
|
5
|
+
module Functions
|
6
|
+
class HeavisideWeightedSplit < WeightedSplit
|
7
|
+
MAX_HASH_VALUE = ('f' * 64).to_i(16)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
protected
|
11
|
+
|
12
|
+
def select_experiment_for(value, experiments)
|
13
|
+
x = value.is_a?(Numeric) ? value : hash(value)
|
14
|
+
|
15
|
+
heaviside(x, experiments)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def hash(value)
|
21
|
+
(sha256(value) - (MAX_HASH_VALUE / 2)).to_f / (MAX_HASH_VALUE / 2)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sha256(value)
|
25
|
+
Digest::SHA256.hexdigest(value.to_s).to_i(16)
|
26
|
+
end
|
27
|
+
|
28
|
+
def heaviside(value, experiments)
|
29
|
+
x = sigmoid(value)
|
30
|
+
accumulated_percentages = 0
|
31
|
+
|
32
|
+
experiments.each do |experiment|
|
33
|
+
accumulated_percentages += percentage(experiment)
|
34
|
+
|
35
|
+
return experiment['name'] if x < accumulated_percentages
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def percentage(experiment)
|
40
|
+
experiment['weight'].to_f / 100
|
41
|
+
end
|
42
|
+
|
43
|
+
def sigmoid(x)
|
44
|
+
BigDecimal.new("#{1.0/(1 + Math.exp(-2 * x))}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require_relative 'weighted_split'
|
3
|
+
|
4
|
+
module ABSplit
|
5
|
+
module Functions
|
6
|
+
class Md5WeightedSplit < WeightedSplit
|
7
|
+
|
8
|
+
MAX_HASH_VALUE = ('f' * 32).to_i(16)
|
9
|
+
|
10
|
+
class << self
|
11
|
+
protected
|
12
|
+
def select_experiment_for(value, experiments)
|
13
|
+
weight = weight(value)
|
14
|
+
experiments = calculate_markers experiments
|
15
|
+
experiments.each do |experiment|
|
16
|
+
return experiment['name'] if weight <= experiment[:marker]
|
17
|
+
end
|
18
|
+
experiments.last['name']
|
19
|
+
end
|
20
|
+
|
21
|
+
def weight(value)
|
22
|
+
100 * hash(value).to_f / MAX_HASH_VALUE
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash(value)
|
26
|
+
Digest::MD5.hexdigest(value.to_s).to_i(16)
|
27
|
+
end
|
28
|
+
|
29
|
+
def calculate_markers(experiments)
|
30
|
+
cumulative = 0
|
31
|
+
experiments.map do |experiment|
|
32
|
+
cumulative += experiment['weight']
|
33
|
+
{ marker: cumulative }.merge(experiment)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -2,61 +2,66 @@ module ABSplit
|
|
2
2
|
module Functions
|
3
3
|
class WeightedSplit
|
4
4
|
MAX_POSITIONS = (9999999999999999999 * 2) + 1 #capacity of Fixnum
|
5
|
-
|
6
|
-
def self.value_for(x, *params)
|
7
|
-
given_weights = validate(params)
|
8
|
-
|
9
|
-
experiments = split_weights(params, params.size, given_weights)
|
10
5
|
|
11
|
-
|
12
|
-
end
|
6
|
+
class << self
|
13
7
|
|
14
|
-
|
8
|
+
def value_for(x, *params)
|
9
|
+
given_weights = validate(params)
|
15
10
|
|
16
|
-
|
17
|
-
given_weights = experiments.each_with_object([]) do |param, memo|
|
18
|
-
memo << param['weight'] if param.has_key?('weight')
|
19
|
-
end
|
11
|
+
experiments = split_weights(params, params.size, given_weights)
|
20
12
|
|
21
|
-
|
22
|
-
raise ABSplit::NoValidExperiment
|
13
|
+
select_experiment_for(x, experiments)
|
23
14
|
end
|
24
15
|
|
25
|
-
|
26
|
-
end
|
16
|
+
protected
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
18
|
+
def validate(experiments)
|
19
|
+
given_weights = experiments.each_with_object([]) do |param, memo|
|
20
|
+
memo << param['weight'] if param.has_key?('weight')
|
21
|
+
end
|
33
22
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
markers(experiments).each_with_index do |limit, i|
|
38
|
-
if x_position <= limit
|
39
|
-
return experiments[i]['name']
|
23
|
+
unless experiments.any? && experiments.size > 1 && given_weights.reduce(0, &:+) <= 100
|
24
|
+
raise ABSplit::NoValidExperiment
|
40
25
|
end
|
26
|
+
|
27
|
+
given_weights
|
41
28
|
end
|
42
29
|
|
43
|
-
experiments
|
44
|
-
|
30
|
+
def split_weights(experiments, parts, given_percentage)
|
31
|
+
return experiments if given_percentage.size >= parts
|
32
|
+
|
33
|
+
missing_weights = parts - given_percentage.size
|
34
|
+
missing_percentage = 100 - given_percentage.reduce(0, &:+)
|
45
35
|
|
46
|
-
|
47
|
-
|
36
|
+
experiments.map do |experiment|
|
37
|
+
if experiment['weight']
|
38
|
+
experiment['weight'] = experiment['weight'].to_f
|
39
|
+
else
|
40
|
+
experiment['weight'] = missing_percentage.to_f / missing_weights.to_f
|
41
|
+
end
|
48
42
|
|
49
|
-
|
50
|
-
|
43
|
+
experiment
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def select_experiment_for(x, experiments)
|
48
|
+
x_position = x.hash
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
experiment['weight'] = missing_percentage.to_f / missing_weights.to_f
|
50
|
+
markers(experiments).each_with_index do |limit, i|
|
51
|
+
if x_position <= limit
|
52
|
+
return experiments[i]['name']
|
53
|
+
end
|
57
54
|
end
|
58
55
|
|
59
|
-
|
56
|
+
experiments.last['name']
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def markers(experiments)
|
62
|
+
experiments.map do |experiment|
|
63
|
+
(MAX_POSITIONS * (experiment['weight'] / 100)) - ( MAX_POSITIONS / 2)
|
64
|
+
end
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
data/lib/a_b_split/test.rb
CHANGED
@@ -1,11 +1,41 @@
|
|
1
1
|
module ABSplit
|
2
2
|
module Test
|
3
|
-
|
4
|
-
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def split(name, x)
|
6
|
+
self.experiment = find(name)
|
5
7
|
|
6
8
|
raise ABSplit::NoValidExperiment unless experiment
|
7
9
|
|
8
|
-
|
10
|
+
function.value_for(x,*options)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_accessor :experiment
|
16
|
+
|
17
|
+
def find(experiment)
|
18
|
+
ABSplit.configuration.experiments[experiment]
|
19
|
+
end
|
20
|
+
|
21
|
+
def function
|
22
|
+
function = 'WeightedSplit'
|
23
|
+
|
24
|
+
unless experiment.is_a?(Array)
|
25
|
+
function = experiment['algorithm'] if experiment['algorithm']
|
26
|
+
|
27
|
+
begin
|
28
|
+
ABSplit::Functions.const_get(function)
|
29
|
+
rescue NameError
|
30
|
+
raise ABSplit::NoValidExperiment
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ABSplit::Functions.const_get(function)
|
35
|
+
end
|
36
|
+
|
37
|
+
def options
|
38
|
+
experiment.is_a?(Array) ? experiment : experiment['options']
|
9
39
|
end
|
10
40
|
end
|
11
41
|
end
|
data/lib/a_b_split.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: a_b_split
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Enrique Figuerola
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -61,7 +61,8 @@ files:
|
|
61
61
|
- lib/a_b_split.rb
|
62
62
|
- lib/a_b_split/configuration.rb
|
63
63
|
- lib/a_b_split/functions.rb
|
64
|
-
- lib/a_b_split/functions/
|
64
|
+
- lib/a_b_split/functions/heaviside_weighted_split.rb
|
65
|
+
- lib/a_b_split/functions/md5_weighted_split.rb
|
65
66
|
- lib/a_b_split/functions/weighted_split.rb
|
66
67
|
- lib/a_b_split/test.rb
|
67
68
|
homepage: https://github.com/emfigo/absplit
|
@@ -76,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
77
|
requirements:
|
77
78
|
- - ">="
|
78
79
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
80
|
+
version: 1.9.3
|
80
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
82
|
requirements:
|
82
83
|
- - ">="
|