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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 68847be6d549eec7d1c38762adf5e8f364b9c6cd
4
- data.tar.gz: 6f2a635a1b583d16628cc869f1fd5821de88dfb6
3
+ metadata.gz: 8d6337e9baf884dc54218a8b6ad030b7256ec30c
4
+ data.tar.gz: e4af93072dfacd1a96f4d73a0182bc0c8a7b0104
5
5
  SHA512:
6
- metadata.gz: d696a619d46b093f519db62503fd7ba00db839f13aa4940d78d1bb0cfb51d927494d642aa991a15e24cb8e0104d225c863f2dc003ca8c490375e95307d5d56da
7
- data.tar.gz: 7dff2394c950bdf30ee68999214c7d2a3c5db7073de1ae6c384504362568b3f20dfaed63821d0a7877021e928592263715e52b695f7ffd9277b2b658884353d8
6
+ metadata.gz: d9e09bbc215026262b7bc48919e99ad3092653967870daedeef421b49453cb4cd0223777dba430d0d4f83243a41711fe3accc4ab3368458bf102c7c3fb9abec1
7
+ data.tar.gz: d21be140d7517554a89346951de8a2141daafa6dcd2b5a93164fd46058f54c73e34e6a467be601307d42fc7f3632d6f776d5f01f1c83ff6fb5adbae8c650eea4
@@ -3,7 +3,7 @@ module ABSplit
3
3
  attr_accessor :experiments
4
4
 
5
5
  def initialize
6
- @experiments = {}
6
+ @experiments = Hash.new
7
7
  end
8
8
  end
9
9
  end
@@ -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
- select_experiment_for(x, experiments)
12
- end
6
+ class << self
13
7
 
14
- private
8
+ def value_for(x, *params)
9
+ given_weights = validate(params)
15
10
 
16
- def self.validate(experiments)
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
- unless experiments.any? && experiments.size > 1 && given_weights.reduce(0, &:+) <= 100
22
- raise ABSplit::NoValidExperiment
13
+ select_experiment_for(x, experiments)
23
14
  end
24
15
 
25
- given_weights
26
- end
16
+ protected
27
17
 
28
- def self.markers(experiments)
29
- experiments.map do |experiment|
30
- (MAX_POSITIONS * (experiment['weight'] / 100)) - ( MAX_POSITIONS / 2)
31
- end
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
- def self.select_experiment_for(x, experiments)
35
- x_position = x.hash
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.last['name']
44
- end
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
- def self.split_weights(experiments, parts, given_percentage)
47
- return experiments if given_percentage.size >= parts
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
- missing_weights = parts - given_percentage.size
50
- missing_percentage = 100 - given_percentage.reduce(0, &:+)
43
+ experiment
44
+ end
45
+ end
46
+
47
+ def select_experiment_for(x, experiments)
48
+ x_position = x.hash
51
49
 
52
- experiments.map do |experiment|
53
- if experiment['weight']
54
- experiment['weight'] = experiment['weight'].to_f
55
- else
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
- experiment
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
@@ -1,11 +1,41 @@
1
1
  module ABSplit
2
2
  module Test
3
- def self.split(name, x)
4
- experiment = ABSplit.configuration.experiments[name]
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
- ABSplit::Functions::WeightedSplit.value_for(x,*experiment)
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
@@ -15,8 +15,8 @@ module ABSplit
15
15
  attr_accessor :configuration
16
16
 
17
17
  def configure
18
- self.configuration ||= Configuration.new
19
-
20
18
  yield(configuration) if block_given?
21
19
  end
22
20
  end
21
+
22
+ ABSplit.configuration = ABSplit::Configuration.new
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.1
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-07-29 00:00:00.000000000 Z
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/sigmoid.rb
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: '0'
80
+ version: 1.9.3
80
81
  required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  requirements:
82
83
  - - ">="
@@ -1,11 +0,0 @@
1
- require 'bigdecimal'
2
-
3
- module ABSplit
4
- module Functions
5
- class Sigmoid
6
- def self.value_for(x, *params)
7
- BigDecimal.new("#{1.0/(1 + Math.exp(-2 * x))}")
8
- end
9
- end
10
- end
11
- end