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 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