loaded_die 2.0.1 → 2.0.3
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 +1 -1
- data/README.txt +3 -2
- data/lib/loaded_die.rb +14 -8
- data/test/loaded_die_test.rb +40 -5
- metadata +2 -18
data/LICENSE.txt
CHANGED
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:
|
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
|
-
|
|
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.
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
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 }
|
data/test/loaded_die_test.rb
CHANGED
|
@@ -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
|
|
105
|
-
fail "" unless 2.0
|
|
106
|
-
fail "" unless (Float(2) + 0.0 + 3.1)
|
|
107
|
-
|
|
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))
|
|
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
|
-
|
|
5
|
-
prerelease:
|
|
6
|
-
segments:
|
|
7
|
-
- 2
|
|
8
|
-
- 0
|
|
9
|
-
- 1
|
|
10
|
-
version: 2.0.1
|
|
4
|
+
version: 2.0.3
|
|
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:
|
|
12
|
+
date: 2026-06-06 00:00:00 -07:00
|
|
19
13
|
default_executable:
|
|
20
14
|
dependencies: []
|
|
21
15
|
|
|
@@ -42,24 +36,14 @@ rdoc_options: []
|
|
|
42
36
|
require_paths:
|
|
43
37
|
- lib
|
|
44
38
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
-
none: false
|
|
46
39
|
requirements:
|
|
47
40
|
- - ">="
|
|
48
41
|
- !ruby/object:Gem::Version
|
|
49
|
-
hash: 57
|
|
50
|
-
segments:
|
|
51
|
-
- 1
|
|
52
|
-
- 8
|
|
53
|
-
- 7
|
|
54
42
|
version: 1.8.7
|
|
55
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
-
none: false
|
|
57
44
|
requirements:
|
|
58
45
|
- - ">="
|
|
59
46
|
- !ruby/object:Gem::Version
|
|
60
|
-
hash: 3
|
|
61
|
-
segments:
|
|
62
|
-
- 0
|
|
63
47
|
version: "0"
|
|
64
48
|
requirements: []
|
|
65
49
|
|