forget_table 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 51b34b5b1d37ac5d704a4d7e48c6b635fb8de7b0
4
+ data.tar.gz: 276ca5952d1e6bdcca27c1c257c8b1fb4b3054d7
5
+ SHA512:
6
+ metadata.gz: d28a09e53676e1b908a8bdcefded130c241b71bdebf5610c48d0e2b25c39d063293f1efc1c82483c8c7552630efc3def2844a103fa98ed0833bb924ad9b03129
7
+ data.tar.gz: 8abb4e1332f2aeac8f2423d09b47fdfd081ae8d86c853915b503662b9d2d9687e3da7865d753faf46dbe89304784dc0a9c1797aad5f7973a901f6058ce54c5cf
@@ -0,0 +1,44 @@
1
+ module ForgetTable
2
+
3
+ class Decay
4
+
5
+ DEFAULT_DECAY_RATE = 0.05
6
+
7
+ # - last_updated: timestamp of the last update
8
+ # - rate: the rate of the decay (optional)
9
+ def initialize(last_updated, rate = DEFAULT_DECAY_RATE)
10
+ @last_updated = last_updated
11
+ @rate = rate
12
+ end
13
+
14
+ def decay_value(value)
15
+ decayed_value = value - decay_for(value)
16
+ decayed_value > 0 ? decayed_value : 1
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :last_updated, :rate
22
+
23
+ def decay_for(value)
24
+ poisson(decay_factor * value)
25
+ end
26
+
27
+ def decay_factor
28
+ rate * tau
29
+ end
30
+
31
+ # Time since last update
32
+ def tau
33
+ [current_time - last_updated, 1].max
34
+ end
35
+
36
+ def poisson(value)
37
+ Poisson.new(value).sample
38
+ end
39
+
40
+ def current_time
41
+ @timestamp ||= Time.now.to_i
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ require 'forget_table/distribution_decrementer'
2
+
3
+ module ForgetTable
4
+
5
+ class Decrementer
6
+
7
+ def initialize(redis, weighted_distribution)
8
+ @redis = redis
9
+ @weighted_distribution = weighted_distribution
10
+ end
11
+
12
+ def run!
13
+ decremented_distribution = distribution_decrementer.decremented_distribution
14
+ updated_redis(decremented_distribution)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :redis, :weighted_distribution
20
+
21
+ # Updates:
22
+ # 1. the weighted distribution with the new distribution
23
+ # 2. the total number of hits for the distribution with the new count
24
+ # 3. the last_updated_at value with the current time
25
+
26
+ def updated_redis(distribution)
27
+ redis.pipelined do
28
+ redis.zadd(distribution.name, distribution.bins.to_a.map(&:reverse))
29
+ redis.set(hits_count_key, distribution.hits_count)
30
+ redis.set(last_updated_at_key, Time.now.to_i)
31
+ end
32
+ end
33
+
34
+ def distribution_decrementer
35
+ last_updated_at = Integer(@redis.get(last_updated_at_key))
36
+ distribution_decrementer = DistributionDecrementer.new(
37
+ weighted_distribution: weighted_distribution,
38
+ last_updated_at: last_updated_at
39
+ )
40
+ end
41
+
42
+ def distribution_keys
43
+ DistributionKeys.new(weighted_distribution.name)
44
+ end
45
+
46
+ def hits_count_key
47
+ distribution_keys.hits_count
48
+ end
49
+
50
+ def last_updated_at_key
51
+ distribution_keys.last_updated_at
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,104 @@
1
+ require 'forget_table/decrementer'
2
+ require 'forget_table/weighted_distribution'
3
+ require 'forget_table/distribution_keys'
4
+
5
+ module ForgetTable
6
+
7
+ # Represents a categorical distribution composed by weighted bins.
8
+ #
9
+ # A distribution is instantiated with the following parameters:
10
+ # - name: the name of the distribution
11
+ # - redis: the redis client that will host the distribution
12
+ #
13
+ # Example of an instance:
14
+ # distribution: "guitars"
15
+ # bins: "fender" => 10, "gibson" => 20, "epi" => "30
16
+
17
+ class Distribution
18
+ attr_reader :name
19
+
20
+ def initialize(name:, redis:)
21
+ @name = name
22
+ @redis = redis
23
+ end
24
+
25
+ # Increments the bin score by the given amount.
26
+ # params:
27
+ # - bin
28
+ # - amount
29
+ def increment(bin:, amount: 1)
30
+ redis.zincrby(name, amount, bin)
31
+
32
+ # Increment the total number of hits
33
+ stored_bins = redis.incrby(hits_count_key, 1)
34
+
35
+ if stored_bins == 1
36
+ # Set the initial timestamp if never set
37
+ redis.set(last_updated_at_key, Time.now.to_i)
38
+ end
39
+ end
40
+
41
+ # Returns the list of bins in the distribution.
42
+ # Params:
43
+ # - number_of_bins
44
+ # - options
45
+ def distribution(number_of_bins: -1, with_scores: false)
46
+ begin
47
+ decrement!
48
+
49
+ stop_bin = (number_of_bins == -1) ? -1 : (number_of_bins - 1)
50
+ redis.zrevrange(name, 0, stop_bin, with_scores: with_scores)
51
+ rescue RuntimeError
52
+ [[]]
53
+ end
54
+ end
55
+
56
+ # Returns the score for the given bin
57
+ def score_for_bin(bin)
58
+ decrement!
59
+
60
+ redis.zscore(name, bin)
61
+ end
62
+
63
+ def last_updated
64
+ redis.get(last_updated_at_key)
65
+ end
66
+
67
+ def hits_count
68
+ redis.get(hits_count_key).to_i
69
+ end
70
+
71
+ private
72
+ attr_reader :redis
73
+
74
+ def hits_count_key
75
+ distribution_keys.hits_count
76
+ end
77
+
78
+ def last_updated_at_key
79
+ distribution_keys.last_updated_at
80
+ end
81
+
82
+ def decrement!
83
+ raise "Cannot find distribution #{name}" unless redis.exists(name)
84
+
85
+ decrementer.run!
86
+ end
87
+
88
+ def decrementer
89
+ Decrementer.new(redis, weighted_distribution)
90
+ end
91
+
92
+ def weighted_distribution
93
+ bins = Hash[*redis.zrevrange(name, 0, -1, with_scores: true).flatten]
94
+ WeightedDistribution.new(
95
+ name: name,
96
+ bins: bins,
97
+ )
98
+ end
99
+
100
+ def distribution_keys
101
+ DistributionKeys.new(name)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ module ForgetTable
2
+
3
+ class DistributionDecrementer
4
+
5
+ def initialize(weighted_distribution:, last_updated_at:)
6
+ @weighted_distribution = weighted_distribution
7
+ @last_updated_at = last_updated_at
8
+ end
9
+
10
+ def decremented_distribution
11
+ WeightedDistribution.new(
12
+ name: weighted_distribution.name,
13
+ bins: decremented_bins
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :weighted_distribution, :last_updated_at
20
+
21
+ def decremented_bins
22
+ decremented_values = decrement(weighted_distribution.values)
23
+ bin_names = weighted_distribution.bin_names
24
+ Hash[*bin_names.zip(decremented_values).flatten]
25
+ end
26
+
27
+ def decrement(values)
28
+ decay = Decay.new(last_updated_at)
29
+ values.map { |value| decay.decay_value(value) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module ForgetTable
2
+
3
+ class DistributionKeys
4
+ attr_reader :distribution_name
5
+
6
+ def initialize(distribution_name)
7
+ @distribution_name = distribution_name
8
+ end
9
+
10
+ def last_updated_at
11
+ "#{distribution_name}/last_updated_at"
12
+ end
13
+
14
+ def hits_count
15
+ "#{distribution_name}/hits_count"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ module ForgetTable
2
+
3
+ class Poisson
4
+ attr_reader :average
5
+
6
+ def initialize(average)
7
+ raise ArgumentError, "average must be > 0 , #{average} given" if average <= 0
8
+ @average = average
9
+ end
10
+
11
+ def sample
12
+ @sample ||= extract_sample
13
+ end
14
+
15
+ private
16
+
17
+ # Returns an Integer extracted from a Poisson
18
+ # distribution with average `average`.
19
+ # Implemented according to the Knuth algorithm.
20
+ def extract_sample
21
+ l = Math.exp(-average)
22
+ k = 0
23
+ p = 1
24
+ while p > l do
25
+ k += 1
26
+ p *= random_in_0_1
27
+ end
28
+ k - 1
29
+ end
30
+
31
+ def random_in_0_1
32
+ random.rand(1.0)
33
+ end
34
+
35
+ def random
36
+ @@rand ||= Random.new
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,23 @@
1
+ module ForgetTable
2
+
3
+ class WeightedDistribution
4
+ attr_reader :name, :bins
5
+
6
+ def initialize(name:, bins:)
7
+ @name = name
8
+ @bins = bins
9
+ end
10
+
11
+ def values
12
+ bins.values
13
+ end
14
+
15
+ def bin_names
16
+ bins.keys
17
+ end
18
+
19
+ def hits_count
20
+ values.inject(:+).to_i
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'redis'
2
+
3
+ require 'forget_table/decay'
4
+ require 'forget_table/decrementer'
5
+ require 'forget_table/distribution'
6
+ require 'forget_table/distribution_decrementer'
7
+ require 'forget_table/distribution_keys'
8
+ require 'forget_table/poisson'
9
+ require 'forget_table/weighted_distribution'
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe ForgetTable::Decay do
4
+
5
+ class FakePoisson
6
+ attr_reader :sample
7
+ def initialize(sample)
8
+ @sample = sample
9
+ end
10
+ end
11
+
12
+ let(:last_updated) { 1_000 }
13
+
14
+ before do
15
+ allow(ForgetTable::Poisson).to receive(:new) { |arg| FakePoisson.new(arg) }
16
+ end
17
+
18
+ describe "#decay" do
19
+ let(:decay) { ForgetTable::Decay.new(last_updated, 0.01) }
20
+
21
+ it "returns new decayed value" do
22
+ set_current_time(1_010)
23
+
24
+ expect(decay.decay_value(10)).to eq(9)
25
+ end
26
+
27
+ it "replaces negative decayed values with 1" do
28
+ set_current_time(10_000)
29
+
30
+ expect(decay.decay_value(20)).to eq(1)
31
+ end
32
+
33
+ def set_current_time(current_time)
34
+ allow(Time).to receive_message_chain(:now, :to_i).and_return(current_time)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'fakeredis'
3
+
4
+ describe ForgetTable::Decrementer do
5
+
6
+ let(:redis) { Redis.new(port: 10000) }
7
+ let(:distribution_name) { "guitars" }
8
+ let(:distribution_keys) { ForgetTable::DistributionKeys.new("guitars") }
9
+
10
+ let(:weighted_distribution) do
11
+ double(:weighted_distribution, name: distribution_name)
12
+ end
13
+
14
+ let(:distribution_decrementer) do
15
+ double(:distribution_decrementer, decremented_distribution: decremented_distribution)
16
+ end
17
+
18
+ let(:decremented_distribution) do
19
+ double(:decremented_distribution,
20
+ name: distribution_name,
21
+ bins: { "fender" => 10.0, "gibson" => 20.0 },
22
+ hits_count: 30.0,
23
+ )
24
+ end
25
+
26
+ let(:decrementer) do
27
+ ForgetTable::Decrementer.new(
28
+ redis,
29
+ weighted_distribution
30
+ )
31
+ end
32
+
33
+ before(:each) do
34
+ redis.flushall
35
+ redis.set(distribution_keys.last_updated_at, 123)
36
+
37
+ allow(ForgetTable::DistributionDecrementer).to receive(:new).
38
+ with(
39
+ weighted_distribution: weighted_distribution,
40
+ last_updated_at: 123
41
+ ) { distribution_decrementer }
42
+ end
43
+
44
+ describe "#run!" do
45
+ it "updates the `hits_count` key after decrementing" do
46
+ decrementer.run!
47
+
48
+ expect(redis.get(distribution_keys.hits_count).to_i).to eq(30)
49
+ end
50
+
51
+ it "updates the timestamp" do
52
+ allow(Time).to receive_message_chain(:now, :to_i) { 37 }
53
+
54
+ decrementer.run!
55
+
56
+ expect(redis.get(distribution_keys.last_updated_at).to_i).to eq(37)
57
+ end
58
+
59
+ it "stores the new values for the distribution" do
60
+ decrementer.run!
61
+
62
+ distribution = redis.zrevrange(distribution_name, 0, -1, with_scores: true)
63
+ expect(distribution).to match_array(
64
+ [
65
+ ["fender", 10.0],
66
+ ["gibson", 20.0],
67
+ ])
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'forget_table/distribution_decrementer'
3
+
4
+ describe ForgetTable::DistributionDecrementer do
5
+
6
+ let(:last_updated_at) { "updated_at" }
7
+ let(:weighted_distribution) do
8
+ double(:dist,
9
+ name: "foo",
10
+ values: [13, 17],
11
+ bin_names: %w(fender gibson)
12
+ )
13
+ end
14
+
15
+ let(:dist_decrementer) do
16
+ ForgetTable::DistributionDecrementer.new(
17
+ weighted_distribution: weighted_distribution,
18
+ last_updated_at: last_updated_at
19
+ )
20
+ end
21
+
22
+ before do
23
+ allow(ForgetTable::Decay).to receive(:new).with(last_updated_at) { |arg| FakeDecay.new(arg) }
24
+ end
25
+
26
+ class FakeDecay
27
+ def initialize(*); end
28
+
29
+ # Just decrement by 1
30
+ def decay_value(value)
31
+ value - 1
32
+ end
33
+ end
34
+
35
+ describe "#decremented_distribution" do
36
+ it "returns a new distribution with the same name" do
37
+ expect(dist_decrementer.decremented_distribution.name).to eq("foo")
38
+ end
39
+
40
+ it "returns a new distribution with decremented bins" do
41
+ decremented_distribution = double
42
+ allow(ForgetTable::WeightedDistribution).to receive(:new).with(
43
+ name: "foo",
44
+ bins: { "fender" => 12, "gibson" => 16 }
45
+ ).and_return(decremented_distribution)
46
+
47
+ expect(dist_decrementer.decremented_distribution).to eq(decremented_distribution)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'fakeredis'
3
+
4
+ describe ForgetTable::Distribution do
5
+
6
+ # Test with a real redis server
7
+ let(:redis) { Redis.new(port: 10000) }
8
+ let(:decrementer) { double(:decrementer) }
9
+ let(:distribution) { ForgetTable::Distribution.new(name: "guitars", redis: redis) }
10
+
11
+ before(:each) do
12
+ redis.flushall
13
+ allow(ForgetTable::Decrementer).to receive(:new).and_return(decrementer)
14
+ allow(decrementer).to receive(:run!)
15
+ end
16
+
17
+ describe "#name" do
18
+ it "reads the correct distribution_name" do
19
+ expect(distribution.name).to eq("guitars")
20
+ end
21
+ end
22
+
23
+ describe "#increment" do
24
+
25
+ it "insert the value if it was not existing before" do
26
+ distribution.increment(bin: "fender", amount: 10)
27
+
28
+ expect(distribution.distribution).to eq(["fender"])
29
+ end
30
+
31
+ it "sets the initial value if the item was not there before" do
32
+ distribution.increment(bin: "fender", amount: 10)
33
+
34
+ expect(distribution.score_for_bin("fender")).to eq(10)
35
+ end
36
+
37
+ it "increments the existing value" do
38
+ distribution.increment(bin: "fender", amount: 10)
39
+ distribution.increment(bin: "fender", amount: 1)
40
+
41
+ expect(distribution.score_for_bin("fender")).to eq(11)
42
+ end
43
+ end
44
+
45
+ describe "#distribution" do
46
+ before do
47
+ distribution.increment(bin: "epiphone", amount: 10)
48
+ distribution.increment(bin: "gibson", amount: 20)
49
+ distribution.increment(bin: "fender", amount: 30)
50
+ end
51
+
52
+ it "returns the list of all stored items" do
53
+ expect(distribution.distribution).to match_array(["epiphone", "gibson", "fender"])
54
+ end
55
+
56
+ it "returns the list of the top n stored item" do
57
+ expect(distribution.distribution(number_of_bins: 2)).to match_array(["gibson", "fender"])
58
+ end
59
+
60
+ it "returns at most the stored elements even if asked for more" do
61
+ expect(distribution.distribution(number_of_bins: 42)).to match_array(["epiphone", "gibson", "fender"])
62
+ end
63
+
64
+ it "accepts a with_scores parameter" do
65
+ expect(distribution.distribution(number_of_bins: 3, with_scores: true)).
66
+ to match_array([["epiphone", 10.0], ["gibson", 20.0], ["fender", 30.0]])
67
+ end
68
+
69
+ it "returns and empty hash if the distribution is not stored in redis" do
70
+ distribution = ForgetTable::Distribution.new(name: "foo", redis: redis)
71
+
72
+ expect(distribution.distribution).to eq([[]])
73
+ end
74
+ end
75
+
76
+ describe "#score_for_bin" do
77
+ it "returns the score for a given stored item" do
78
+ distribution.increment(bin: "ibanez", amount: 37)
79
+
80
+ expect(distribution.score_for_bin("ibanez")).to eq(37)
81
+ end
82
+
83
+ it "raises an exception if the distribution is not stored in redis" do
84
+ distribution = ForgetTable::Distribution.new(name: "foo", redis: redis)
85
+
86
+ expect{ distribution.score_for_bin("yolo") }.to raise_error
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe ForgetTable::Poisson do
4
+
5
+ context ".sample" do
6
+
7
+ REPETITIONS = 1000
8
+
9
+ it "raises an exception if average is negative" do
10
+ expect { ForgetTable::Poisson.new(-1) }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it "should always generate an integer" do
14
+ REPETITIONS.times do
15
+ expect(ForgetTable::Poisson.new(37).sample).to be_kind_of(Integer)
16
+ end
17
+ end
18
+
19
+ it "should always be within the acceptance range" do
20
+ sum = 0
21
+ REPETITIONS.times do
22
+ sum += ForgetTable::Poisson.new(37).sample
23
+ end
24
+ sum /= REPETITIONS.to_f
25
+
26
+ expect(sum).to be_within(1).of(37)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'forget_table/weighted_distribution'
3
+
4
+ describe ForgetTable::WeightedDistribution do
5
+
6
+ let(:distribution) do
7
+ ForgetTable::WeightedDistribution.new(
8
+ name: "foo",
9
+ bins: { "fender" => 10, "gibson" => 20 }
10
+ )
11
+ end
12
+
13
+ it "returns the distribution name" do
14
+ expect(distribution.name).to eq("foo")
15
+ end
16
+
17
+ it "returns the array of values" do
18
+ expect(distribution.values).to eq([10, 20])
19
+ end
20
+
21
+ it "returns the array of bin names" do
22
+ expect(distribution.bin_names).to eq(["fender", "gibson"])
23
+ end
24
+
25
+ it "returns the total number of hits" do
26
+ expect(distribution.hits_count).to eq(30)
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'fakeredis'
3
+
4
+ describe "One bin distribution" do
5
+
6
+ let(:redis) { Redis.new(port: 10000) }
7
+ let(:distribution) { ForgetTable::Distribution.new(name: "guitars", redis: redis) }
8
+
9
+ before do
10
+ redis.flushall
11
+ end
12
+
13
+ describe "single bin" do
14
+ context "with a single increment" do
15
+ before do
16
+ distribution.increment(bin: "fender", amount: 10)
17
+ end
18
+
19
+ it "returns the only bin" do
20
+ expect(distribution.distribution).to eq(["fender"])
21
+ end
22
+
23
+ it "returns a positive score for the bin" do
24
+ expect(distribution.score_for_bin("fender")).to be > 0
25
+ end
26
+
27
+ it "returns a positive number of hits" do
28
+ expect(distribution.hits_count).to be > 0
29
+ end
30
+ end
31
+
32
+ context "with multiple increment" do
33
+ before do
34
+ distribution.increment(bin: "fender", amount: 10)
35
+ distribution.increment(bin: "fender", amount: 20)
36
+ distribution.increment(bin: "fender", amount: 30)
37
+ end
38
+
39
+ it "returns the only bin" do
40
+ expect(distribution.distribution).to eq(["fender"])
41
+ end
42
+
43
+ it "returns a positive score for the bin" do
44
+ expect(distribution.score_for_bin("fender")).to be > 0
45
+ end
46
+
47
+ it "returns a positive number of hits" do
48
+ expect(distribution.hits_count).to be > 0
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "multiple bins" do
54
+ context "with single increment" do
55
+ before do
56
+ distribution.increment(bin: "fender", amount: 10)
57
+ distribution.increment(bin: "gibson", amount: 20)
58
+ end
59
+
60
+ it "returns both bins" do
61
+ expect(distribution.distribution).to match_array(["fender", "gibson"])
62
+ end
63
+
64
+ it "returns a positive score for the bins" do
65
+ expect(distribution.score_for_bin("fender")).to be > 0
66
+ expect(distribution.score_for_bin("gibson")).to be > 0
67
+ end
68
+
69
+ it "respects the scoring order" do
70
+ fender_score = distribution.score_for_bin("fender")
71
+ gibson_score = distribution.score_for_bin("gibson")
72
+
73
+ expect(gibson_score).to be >= fender_score
74
+ end
75
+
76
+ it "returns a positive number of hits" do
77
+ expect(distribution.hits_count).to be > 0
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ require 'forget_table'
2
+
3
+ require 'byebug'
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forget_table
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicolò Calcavecchia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakeredis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: An implementation of http://word.bitly.com/post/41284219720/forget-table
70
+ in ruby
71
+ email: calcavecchia@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/forget_table.rb
77
+ - lib/forget_table/decay.rb
78
+ - lib/forget_table/decrementer.rb
79
+ - lib/forget_table/distribution.rb
80
+ - lib/forget_table/distribution_decrementer.rb
81
+ - lib/forget_table/distribution_keys.rb
82
+ - lib/forget_table/poisson.rb
83
+ - lib/forget_table/weighted_distribution.rb
84
+ - spec/forget_table/decay_spec.rb
85
+ - spec/forget_table/decrementer_spec.rb
86
+ - spec/forget_table/distribution_decrementer_spec.rb
87
+ - spec/forget_table/distribution_spec.rb
88
+ - spec/forget_table/poisson_spec.rb
89
+ - spec/forget_table/weighted_distribution_spec.rb
90
+ - spec/integration/integration_spec.rb
91
+ - spec/spec_helper.rb
92
+ homepage: https://github.com/ncalca/forgettable
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.2.2
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Keep track of dynamically changing categorical distribution
116
+ test_files: []
117
+ has_rdoc: