a_b_split 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|