categorical_distribution 0.1.0 → 0.2.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: 53bacc10077bb5dc4e4662c482f5f7e998574704
4
- data.tar.gz: 096ebc9b023c6b842bbe60b1ef6fab7f412d0521
3
+ metadata.gz: 4b0487b59cbaa4f41e4e40295a2d9ff73513b387
4
+ data.tar.gz: 68871338ae32391f43e5c6ad857656a98f692483
5
5
  SHA512:
6
- metadata.gz: 296d5956f4d2dcf59d032fb6f10f809407e27aeb6557d9949a60fc76abc8f459618890a9d3d303f9ac57e167d61a0f72e97fedce6b992b10cb9a011326a12aab
7
- data.tar.gz: e1de5a78bc59289acf47978b1a0010b95841cd33349f839cd4ba62e6f8f24b5e82d05ba2f9061bf04a3e9594b170968887b0b5365da1a091028287126883b6fe
6
+ metadata.gz: 3a65b3985a695b1010da47f397cd4537e2226a159ce02abb25715699efb258938251117b81597b2339cfbd809e0dcf48b1709d07a5fb500e0b83332b0778d581
7
+ data.tar.gz: ed9d93b77779ba444b974447d3c82c37c90a6217fbf8153c20219f94b16bfac26ecd1d36cc559d0603eb8bfcc69c19005ceef269c6762619ce55c5549e630ba1
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/zanecodes/categorical_distribution"
14
14
  spec.license = "MIT"
15
15
 
16
+ spec.required_ruby_version = '>= 1.9'
17
+
16
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
19
  f.match(%r{^(test|spec|features)/})
18
20
  end
@@ -1,5 +1,3 @@
1
- require 'forwardable'
2
-
3
1
  # Generate values from a categorical probability distribution in constant time.
4
2
  # Uses Vose's Alias Method to construct a table of probabilities and aliases.
5
3
  # To generate a value, we pick an entry in the distribution at random, and then pick
@@ -10,10 +8,8 @@ require 'forwardable'
10
8
  # See http://www.keithschwarz.com/darts-dice-coins for a detailed explanation.
11
9
 
12
10
  class CategoricalDistribution
13
- extend Forwardable
14
11
  include Enumerable
15
12
 
16
-
17
13
  # :call-seq:
18
14
  # CategoricalDistribution.new(p={})
19
15
  # CategoricalDistribution.new(p=[])
@@ -49,22 +45,20 @@ class CategoricalDistribution
49
45
  def initialize(p={}, values=nil)
50
46
  if p.respond_to?(:keys) && p.respond_to?(:values)
51
47
  values, p = p.keys, p.values
52
- else
53
- values ||= p.each_index.to_a
54
48
  end
55
49
 
50
+ @size = p.size
51
+ @values = values
52
+
56
53
  raise ArgumentError, 'probabilities must be positive' if p.any? { |value| value < 0 }
57
54
 
58
55
  sum = p.reduce(:+)
59
56
  p.map! { |value| Rational(value, sum) } unless sum == 1
60
57
 
61
- @p = Hash[[values, p].transpose].freeze
62
-
63
- n = p.size
64
- @prob = Array.new(n, Rational(1))
65
- @alias = Array.new(n)
58
+ @prob = Array.new(size, Rational(1))
59
+ @alias = Array.new(size)
66
60
 
67
- p.map! { |value| value * n }
61
+ p.map! { |value| value * size }
68
62
 
69
63
  small, large = p.each_index.partition { |i| p[i] < 1 }
70
64
 
@@ -95,10 +89,11 @@ class CategoricalDistribution
95
89
  # distribution.rand #=> :c
96
90
 
97
91
  def rand(random: Random)
98
- return if @prob.empty?
92
+ return if empty?
99
93
 
100
- i = random.rand(@prob.size)
101
- random.rand <= @prob[i] ? @p.keys[i] : @p.keys[@alias[i]]
94
+ i = random.rand(size)
95
+ index = random.rand <= @prob[i] ? i : @alias[i]
96
+ @values.nil? ? index : @values[index]
102
97
  end
103
98
 
104
99
 
@@ -127,6 +122,7 @@ class CategoricalDistribution
127
122
 
128
123
  # Returns the distribution's values and their probabilities, as a hash of
129
124
  # objects to Rationals which sum to one, or an empty hash if the distribution is empty.
125
+ # This operation is expensive, in exchange for memory efficiency.
130
126
  #
131
127
  # CategoricalDistribution.new([1, 1, 1]).probabilities
132
128
  # #=> {0 => (1/3), 1 => (1/3), 2 => (1/3)}
@@ -135,7 +131,10 @@ class CategoricalDistribution
135
131
  # #=> {}
136
132
 
137
133
  def probabilities
138
- @p
134
+ p = @prob.clone
135
+ @alias.each_with_index { |a, i| p[a] += 1 - @prob[i] unless a.nil? }
136
+ p.map! { |prob| prob / size }
137
+ Hash[[@values || size.times.to_a, p].transpose]
139
138
  end
140
139
 
141
140
 
@@ -159,7 +158,9 @@ class CategoricalDistribution
159
158
  # #=> false
160
159
 
161
160
  def ==(other)
162
- self.probabilities == other.probabilities
161
+ self.prob == other.prob &&
162
+ self.alias == other.alias &&
163
+ self.values == other.values
163
164
  end
164
165
 
165
166
 
@@ -171,14 +172,35 @@ class CategoricalDistribution
171
172
 
172
173
  def eql?(other)
173
174
  self.equal?(other) ||
174
- (self.class == other.class && self.probabilities == other.probabilities)
175
+ (self.class == other.class &&
176
+ self.prob == other.prob &&
177
+ self.alias == other.alias &&
178
+ self.values == other.values)
175
179
  end
176
180
 
177
181
 
178
- def_delegators :@p, :empty?, :hash, :length, :size, :to_s
182
+ # :call_seq:
183
+ # distribution.hash -> fixnum
184
+ #
185
+ # Compute a hash-code for this probability distribution.
186
+ #
187
+ # Two distributions with the same values and probabilities will have the same
188
+ # hash code (and will compare using #eql?).
189
+
190
+ def hash
191
+ hash = 17
192
+ hash = 37 * hash + @prob.hash
193
+ hash = 37 * hash + @alias.hash
194
+ hash = 37 * hash + @values.hash
195
+ hash
196
+ end
197
+
198
+
199
+ def to_s
200
+ probabilities.to_s
201
+ end
202
+
179
203
 
180
- ##
181
- # :method: empty?
182
204
  # :call_seq:
183
205
  # distribution.empty? -> true or false
184
206
  #
@@ -187,17 +209,9 @@ class CategoricalDistribution
187
209
  # distribution = CategoricalDistribution.new
188
210
  # distribution.empty? #=> true
189
211
 
190
- ##
191
- # :method: hash
192
- # :call_seq:
193
- # distribution.hash -> fixnum
194
- #
195
- # Compute a hash-code for this probability distribution.
196
- #
197
- # Two distributions with the same values and probabilities will have the same
198
- # hash code (and will compare using #eql?).
199
- #
200
- # See also Hash#hash.
212
+ def empty?
213
+ size.zero?
214
+ end
201
215
 
202
216
  ##
203
217
  # :method: length
@@ -208,4 +222,10 @@ class CategoricalDistribution
208
222
  #
209
223
  # CategoricalDistribution.new([1, 2, 3, 4]).length #=> 4
210
224
  # CategoricalDistribution.new.length #=> 0
225
+
226
+ attr_reader :size
227
+ alias length size
228
+
229
+ protected
230
+ attr_reader :prob, :alias, :values
211
231
  end
@@ -1,3 +1,3 @@
1
1
  module CategoricalDistribution
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: categorical_distribution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zane Geiger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-15 00:00:00.000000000 Z
11
+ date: 2016-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -82,7 +82,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
82
  requirements:
83
83
  - - ">="
84
84
  - !ruby/object:Gem::Version
85
- version: '0'
85
+ version: '1.9'
86
86
  required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="