loaded_die 2.0.0 → 2.0.4

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.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024 Aaron Beckerman
1
+ Copyright (c) 2015-2026 Aaron Beckerman
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of
4
4
  this software and associated documentation files (the "Software"), to deal in
data/README.txt CHANGED
@@ -1,7 +1,8 @@
1
1
  Loaded Die
2
2
 
3
3
  Loaded Die is a Ruby library that makes it easy to randomly choose from a set
4
- of options where some options are more likely than others.
4
+ of options where some options are more likely than others. It's written for
5
+ clarity over performance.
5
6
 
6
7
 
7
8
  Usage
@@ -12,7 +13,7 @@ times as you like.
12
13
 
13
14
  Let's say you want to choose randomly from three strings: "A", "B", and "C".
14
15
  You want "A" and "B" to be equally likely, and "C" to be twice as likely as one
15
- of them. That means "A" should be 25% likely, B should be 25% likely, and C
16
+ of them. That means "A" should be 25% likely, "B" should be 25% likely, and "C"
16
17
  should be 50% likely.
17
18
 
18
19
  You can create your sampler like this:
@@ -37,8 +38,8 @@ supply as the argument a hash with your random number generator under the
37
38
  :random key. (This is based on the behavior of Ruby's Array#sample.) Your
38
39
  random number generator will be sent a rand message with one argument, the sum
39
40
  of the weights you specified when creating the sampler. The returned object
40
- must be a float greater than or equal to zero and less than the sum of the
41
- weights.
41
+ must be a float, or convert to one, greater than or equal to zero and less than
42
+ the sum of the weights.
42
43
 
43
44
  rng = Object.new
44
45
  def rng.rand(n)
data/lib/loaded_die.rb CHANGED
@@ -28,7 +28,7 @@ module LoadedDie
28
28
  # is an individual that can be chosen and the second element is its weight
29
29
  # -- that is, the likelihood relative to the other weights that the
30
30
  # individual will be chosen. Weights must convert to finite floats that are
31
- # zero or positive.
31
+ # zero or positive. Their sum must also be finite.
32
32
 
33
33
  def initialize population
34
34
  @compiled = population.to_enum.inject [] do |accum, elem|
@@ -45,7 +45,12 @@ module LoadedDie
45
45
  else
46
46
  0.0
47
47
  end
48
- accum << { :maximum => prev_max + weight_f, :individual => individual }
48
+ maximum = prev_max + weight_f
49
+ unless maximum.finite?
50
+ ::Kernel.raise ::ArgumentError, "invalid total weight #{maximum}"
51
+ break
52
+ end
53
+ accum << { :maximum => maximum, :individual => individual }
49
54
  end
50
55
  nil
51
56
  end
@@ -80,12 +85,13 @@ module LoadedDie
80
85
  end
81
86
 
82
87
  ##
83
- # Returns a randomly-chosen individual. The argument is a hash, empty by
84
- # default. If it contains a value for the +:random+ key, that value will be
85
- # used as the random number generator instead of the default. This RNG will
86
- # be sent a rand message with one argument, the sum of weights, and should
87
- # return a number (convertible to a float) greater than or equal to zero
88
- # and less than the sum of weights.
88
+ # Returns a randomly-chosen individual. If the total weight is zero, this
89
+ # method returns nil. The argument is a hash, empty by default. If it
90
+ # contains a value for the +:random+ key, that value will be used as the
91
+ # random number generator instead of the default. This RNG will be sent a
92
+ # rand message with one argument, the sum of weights, and should return a
93
+ # number (convertible to a float) greater than or equal to zero and less
94
+ # than the sum of weights.
89
95
 
90
96
  def sample options = {}
91
97
  rng = options.fetch(:random) { ::LoadedDie::Sampler::DEFAULT_RNG }
@@ -26,6 +26,25 @@ fail "" unless LoadedDie::Sampler.equal? LoadedDie::Sampler.new({}).class
26
26
  fail "" unless LoadedDie::Sampler.equal?(
27
27
  LoadedDie::Sampler.new({ :a => 0.0, :b => 1.0 }).class)
28
28
 
29
+ # LoadedDie::Sampler.new should allow population elements to have more than
30
+ # two elements. It should ignore extras.
31
+ fail "" unless 1.0.eql? LoadedDie::Sampler.new([[:a, 1.0, "ignored"]]).length
32
+
33
+ # LoadedDie::Sampler.new should allow the population to be anything that
34
+ # returns an enumerable when sent a to_enum message with no arguments.
35
+ class ::Object
36
+ pop = ::Object.new
37
+ def pop.to_enum
38
+ [[:a, 1.0]]
39
+ end
40
+ fail "" unless 1.0.eql? LoadedDie::Sampler.new(pop).length
41
+ end
42
+
43
+ # LoadedDie::Sampler.new should allow weights to be objects that convert to
44
+ # floats in a conventional way.
45
+ fail "" unless 1.0.eql? LoadedDie::Sampler.new({ :a => 1 }).length
46
+ fail "" unless 1.7.eql? LoadedDie::Sampler.new({ :a => "1.7" }).length
47
+
29
48
  # LoadedDie::Sampler.new with a negative weight should raise an exception.
30
49
  begin
31
50
  LoadedDie::Sampler.new({ :a => -1.0 })
@@ -53,6 +72,16 @@ else
53
72
  fail ""
54
73
  end
55
74
 
75
+ # LoadedDie::Sampler.new with finite weights whose sum is not finite should
76
+ # raise an exception.
77
+ begin
78
+ LoadedDie::Sampler.new({ :a => Float::MAX, :b => Float::MAX })
79
+ rescue ArgumentError
80
+ fail "" unless /\Ainvalid total weight / =~ $!.message
81
+ else
82
+ fail ""
83
+ end
84
+
56
85
  # LoadedDie::Sampler.new with a weight that does not convert to a float should
57
86
  # raise an exception.
58
87
  begin
@@ -61,6 +90,12 @@ rescue TypeError
61
90
  else
62
91
  fail ""
63
92
  end
93
+ begin
94
+ LoadedDie::Sampler.new({ :a => "z" })
95
+ rescue ArgumentError
96
+ else
97
+ fail ""
98
+ end
64
99
 
65
100
  # LoadedDie::Sampler.new with a population that doesn't conform to the
66
101
  # interface should raise an exception.
@@ -101,10 +136,10 @@ class ::Object
101
136
  end
102
137
 
103
138
  # Sampler length should be the sum of weights converted to floats.
104
- fail "" unless 0.0 == LoadedDie::Sampler.new([]).length
105
- fail "" unless 2.0 == LoadedDie::Sampler.new([[:a, 2.0]]).length
106
- fail "" unless (Float(2) + 0.0 + 3.1) ==
107
- LoadedDie::Sampler.new([[:a, 2], [:b, 0.0], [:c, 3.1]]).length
139
+ fail "" unless 0.0.eql? LoadedDie::Sampler.new([]).length
140
+ fail "" unless 2.0.eql? LoadedDie::Sampler.new([[:a, 2.0]]).length
141
+ fail "" unless (Float(2) + 0.0 + 3.1).eql? LoadedDie::Sampler.new(
142
+ [[:a, 2], [:b, 0.0], [:c, 3.1]]).length
108
143
 
109
144
  # Sending length with superfluous non-block arguments to a sampler should raise
110
145
  # an exception.
@@ -172,7 +207,7 @@ class ::Object
172
207
  sampler = LoadedDie::Sampler.new [[:a, 3], [:b, 2.0], ["!", 0.0], [:c, 9001]]
173
208
  rng = Object.new
174
209
  def rng.rand n
175
- fail "" unless ((Float(3) + 2.0) + Float(9001)) == n
210
+ fail "" unless ((Float(3) + 2.0) + Float(9001)).eql? n
176
211
  4.9
177
212
  end
178
213
  fail "" unless :b.equal? sampler.sample({ :random => rng })
metadata CHANGED
@@ -1,13 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loaded_die
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
5
- prerelease:
6
- segments:
7
- - 2
8
- - 0
9
- - 0
10
- version: 2.0.0
4
+ version: 2.0.4
11
5
  platform: ruby
12
6
  authors:
13
7
  - Aaron Beckerman
@@ -15,7 +9,7 @@ autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
11
 
18
- date: 2024-03-12 00:00:00 -07:00
12
+ date: 2026-07-03 00:00:00 -07:00
19
13
  default_executable:
20
14
  dependencies: []
21
15
 
@@ -30,7 +24,6 @@ extra_rdoc_files: []
30
24
  files:
31
25
  - LICENSE.txt
32
26
  - README.txt
33
- - loaded_die.gemspec
34
27
  - lib/loaded_die.rb
35
28
  - test/loaded_die_test.rb
36
29
  has_rdoc: true
@@ -43,24 +36,14 @@ rdoc_options: []
43
36
  require_paths:
44
37
  - lib
45
38
  required_ruby_version: !ruby/object:Gem::Requirement
46
- none: false
47
39
  requirements:
48
40
  - - ">="
49
41
  - !ruby/object:Gem::Version
50
- hash: 57
51
- segments:
52
- - 1
53
- - 8
54
- - 7
55
42
  version: 1.8.7
56
43
  required_rubygems_version: !ruby/object:Gem::Requirement
57
- none: false
58
44
  requirements:
59
45
  - - ">="
60
46
  - !ruby/object:Gem::Version
61
- hash: 3
62
- segments:
63
- - 0
64
47
  version: "0"
65
48
  requirements: []
66
49
 
@@ -68,6 +51,6 @@ rubyforge_project:
68
51
  rubygems_version: 1.6.2
69
52
  signing_key:
70
53
  specification_version: 3
71
- summary: A library for choosing randomly where some options are more likely than others.
72
- test_files:
73
- - test/loaded_die_test.rb
54
+ summary: A library for choosing randomly where some options are more likely than others
55
+ test_files: []
56
+
data/loaded_die.gemspec DELETED
@@ -1,12 +0,0 @@
1
- Gem::Specification.new do |s|
2
- s.name = "loaded_die"
3
- s.version = "2.0.0"
4
- s.authors = ["Aaron Beckerman"]
5
- s.summary = "A library for choosing randomly where some options are more" \
6
- " likely than others."
7
- s.licenses = ["MIT"]
8
- s.required_ruby_version = ">= 1.8.7"
9
- s.files = ["LICENSE.txt", "README.txt", "loaded_die.gemspec",
10
- "lib/loaded_die.rb", "test/loaded_die_test.rb"]
11
- s.test_files = ["test/loaded_die_test.rb"]
12
- end