aliastable 3.0.2 → 3.0.3

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: 95ea83614738c6b2c92b8db5abc1afd806f05d39
4
- data.tar.gz: 780af8b387b6caf1cdf6597b6a551cb2aca25f5f
3
+ metadata.gz: 64997be896934a0e70ba0c4776e26f08a68fae50
4
+ data.tar.gz: 7e7d8a38a21f8355be9c0e83559fb3e5689ea556
5
5
  SHA512:
6
- metadata.gz: 336a3bec2883cd91589968dd0b5a9c79274b3278d074ecd7301c9f4b8fd2edae0daf9c55d8244a625cd0a2b0092d79fc2a4a7b448fe9ff0c07e90215c2ac1099
7
- data.tar.gz: 3232ecaf98cbda4cc48d45989da5b62d08c68836b774bfb8438f4a95a90065a86ab0a57d60c50a7128255bf609c47d24f07ff8adfa23ef7d2650d2ff9897de29
6
+ metadata.gz: e49595d9a6da320b2e22db5672f5fc4546a4919edc3915dbd6e9b1cec69beff947b16006f76a55727c147946dd08cec4f2aeaad4be31df6e9a606de27190ed6d
7
+ data.tar.gz: f696a2b2f0b5de5eb7b9932f4a121f0b1e94e988284eb0123e79c45bef8e59dee1c3b089ab5e24ddd448ab17e22acfe367cbb52e8b3130e7745307c3bd4ae638
@@ -1,17 +1,17 @@
1
1
  # -*- ruby -*-
2
- _VERSION = "3.0.2"
2
+ _VERSION = "3.0.3"
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "aliastable"
6
6
  s.version = _VERSION
7
- s.date = "2015-04-19"
7
+ s.date = "2015-09-10"
8
8
  s.summary = "Efficiently generate random outcomes from an arbitrary categorical distribution."
9
9
  s.email = "pjs@alum.mit.edu"
10
10
  s.description = "If a categorical distribution has k distinct values, traditional approaches will require O(k) work to pick an outcome with the correct probabilities. This algorithm uses conditional probability to construct a table which will yield outcomes with the correct probabilities, but in O(1) time."
11
11
  s.author = "Paul J Sanchez"
12
12
  s.files = %w[
13
13
  aliastable.gemspec
14
- lib/alias.rb
14
+ lib/aliastable.rb
15
15
  Rakefile
16
16
  test/infile.bad.1
17
17
  test/infile.bad.2
@@ -12,55 +12,47 @@ class AliasTable
12
12
  # Construct an alias table from a set of values and their associated
13
13
  # probabilities. Values and their probabilities must be synchronized,
14
14
  # i.e., they must be arrays of the same length. Values can be
15
- # anything, but the probabilities must be positive numbers that
16
- # sum to one.
15
+ # anything, but the probabilities must be positive Rational numbers
16
+ # that sum to one.
17
17
  #
18
18
  # *Arguments*::
19
- # - +x_set+ -> the set of values to generate from.
19
+ # - +x_set+ -> the set of values from which to generate.
20
20
  # - +p_value+ -> the synchronized set of probabilities associated
21
21
  # with the value set. These values should be Rationals to avoid
22
22
  # rounding errors.
23
23
  # *Raises*::
24
24
  # - RuntimeError if +x_set+ and +p_value+s are different lengths.
25
- # - RuntimeError if any +p_value+ are negative.
26
- # - RuntimeError if +p_value+ don't sum to one. Use Rationals to avoid this.
25
+ # - RuntimeError if any +p_value+ is negative.
26
+ # - RuntimeError if +p_value+s don't sum to one. Rationals will avoid this.
27
27
  #
28
- def initialize(x_values, p_values)
29
- if x_values.length != p_values.length
30
- raise "Args to AliasTable must be vectors of the same length."
28
+ def initialize(x_set, p_value)
29
+ if x_set.length != p_value.length
30
+ fail 'Args to AliasTable must be vectors of the same length.'
31
31
  end
32
- p_val = p_values.map do |current_p|
33
- tmp = current_p.to_r
34
- raise "p_values must be positive" if tmp <= 0.0
35
- tmp
36
- end
37
- unless p_val.reduce(:+) == Rational(1)
38
- raise "p_values must sum to 1.0"
39
- end
40
- @x = x_values.clone.freeze
32
+ fail 'p_values must be positive' unless p_value.all? { |value| value > 0 }
33
+ @p_primary = p_value.map(&:to_r)
34
+ fail 'p_values must sum to 1' unless @p_primary.reduce(:+) == Rational(1)
35
+ @x = x_set.clone.freeze
41
36
  @alias = Array.new(@x.length)
42
- @p_primary = Array.new(@x.length).map{Rational(1)}
43
- equiprob = Rational(1, @x.length)
44
- deficit_set = []
45
- surplus_set = []
46
- @x.each_index do |i|
47
- unless p_val[i] == equiprob
48
- (p_val[i] < equiprob ? deficit_set : surplus_set) << i
49
- end
50
- end
51
- until deficit_set.empty? do
37
+ parity = Rational(1, @x.length)
38
+ group = @p_primary.each_index.group_by { |i| @p_primary[i] <=> parity }
39
+ deficit_set = group[-1]
40
+ surplus_set = group[1]
41
+ until deficit_set.empty?
52
42
  deficit = deficit_set.pop
53
43
  surplus = surplus_set.pop
54
- @p_primary[deficit] = p_val[deficit] / equiprob
44
+ @p_primary[surplus] -= parity - @p_primary[deficit]
45
+ @p_primary[deficit] /= parity
55
46
  @alias[deficit] = @x[surplus]
56
- p_val[surplus] -= equiprob - p_val[deficit]
57
- unless p_val[surplus] == equiprob
58
- (p_val[surplus] < equiprob ? deficit_set : surplus_set) << surplus
47
+ if @p_primary[surplus] == parity
48
+ @p_primary[surplus] = Rational(1)
49
+ else
50
+ (@p_primary[surplus] < parity ? deficit_set : surplus_set) << surplus
59
51
  end
60
52
  end
61
53
  end
62
54
 
63
- # Returns a random outcome from this object's distribution.
55
+ # Return a random outcome from this object's distribution.
64
56
  # The generate method is O(1) time, but is not an inversion
65
57
  # since two uniforms are used for each value that gets generated.
66
58
  #
@@ -68,5 +60,4 @@ class AliasTable
68
60
  column = rand(@x.length)
69
61
  rand <= @p_primary[column] ? @x[column] : @alias[column]
70
62
  end
71
-
72
63
  end
@@ -1,42 +1,42 @@
1
1
  #!/usr/bin/env ruby -w
2
2
 
3
- require 'alias'
3
+ require_relative '../lib/aliastable.rb'
4
4
 
5
5
  nvars = 1_000_000
6
6
  begin
7
- at = AliasTable.new(["yes", "no"], [0.3, 0.3, 0.4])
8
- nvars.times {print at.generate, "\n"}
9
- rescue Exception => e
10
- puts e.message
7
+ at = AliasTable.new(%w(yes no), [0.3, 0.3, 0.4])
8
+ nvars.times { print at.generate, "\n" }
9
+ rescue RuntimeError => e
10
+ p e
11
11
  puts
12
12
  end
13
- Dir["test/infile.*"].each do |f_name|
13
+ Dir['test/infile.*'].each do |f_name|
14
14
  x = []
15
15
  probs = []
16
- f = File.open(f_name, "r")
16
+ f = File.open(f_name, 'r')
17
17
  counts = {}
18
18
  expected_counts = {}
19
- while line = f.gets do
19
+ while line = f.gets
20
20
  inputs = line.strip.split(/[\s,;:]+/)
21
21
  x << inputs[0]
22
22
  counts[inputs[0]] = 0
23
23
  probs << inputs[1].to_r
24
24
  n_hat = probs[-1] * nvars
25
- half_width = 2.5 * Math::sqrt(n_hat * (1.0 - probs[-1])) if n_hat > 0
25
+ half_width = 2.5 * Math.sqrt(n_hat * (1.0 - probs[-1])) if n_hat > 0
26
26
  expected_counts[inputs[0]] = [n_hat, half_width]
27
27
  end
28
28
  f.close
29
29
  begin
30
30
  at = AliasTable.new(x, probs)
31
- nvars.times {counts[at.generate] += 1}
32
- puts "All values should be in range almost always:"
31
+ nvars.times { counts[at.generate] += 1 }
32
+ puts 'All values should be in range almost always:'
33
33
  counts.each_key do |k|
34
34
  printf "%s: Allowable Range = %d, Expected - Observed = %d\n",
35
- k, expected_counts[k][1], expected_counts[k][0] - counts[k]
35
+ k, expected_counts[k][1], expected_counts[k][0] - counts[k]
36
36
  end
37
37
  puts
38
- rescue Exception => e
39
- puts e.message
38
+ rescue RuntimeError => e
39
+ p e
40
40
  puts
41
41
  end
42
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aliastable
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul J Sanchez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-19 00:00:00.000000000 Z
11
+ date: 2015-09-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: If a categorical distribution has k distinct values, traditional approaches
14
14
  will require O(k) work to pick an outcome with the correct probabilities. This
@@ -21,7 +21,7 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - Rakefile
23
23
  - aliastable.gemspec
24
- - lib/alias.rb
24
+ - lib/aliastable.rb
25
25
  - test/infile.bad.1
26
26
  - test/infile.bad.2
27
27
  - test/infile.bad.3
@@ -49,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
49
  version: '0'
50
50
  requirements: []
51
51
  rubyforge_project:
52
- rubygems_version: 2.4.5
52
+ rubygems_version: 2.4.5.1
53
53
  signing_key:
54
54
  specification_version: 4
55
55
  summary: Efficiently generate random outcomes from an arbitrary categorical distribution.