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 +4 -4
- data/categorical_distribution.gemspec +2 -0
- data/lib/categorical_distribution.rb +52 -32
- data/lib/categorical_distribution/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b0487b59cbaa4f41e4e40295a2d9ff73513b387
|
|
4
|
+
data.tar.gz: 68871338ae32391f43e5c6ad857656a98f692483
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
@
|
|
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 *
|
|
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
|
|
92
|
+
return if empty?
|
|
99
93
|
|
|
100
|
-
i = random.rand(
|
|
101
|
-
random.rand <= @prob[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
|
-
@
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
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.
|
|
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-
|
|
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: '
|
|
85
|
+
version: '1.9'
|
|
86
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
87
|
requirements:
|
|
88
88
|
- - ">="
|