hazard 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/README.md +16 -1
- data/hazard.gemspec +1 -1
- data/lib/hazard/version.rb +1 -1
- data/lib/weighted_table.rb +32 -16
- data/test/weighted_table_test.rb +158 -88
- 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: ccb0217f3b63ffd8b667acb3ca8250145540120e
|
4
|
+
data.tar.gz: c0b892b16371e60d4e4a0c71e42c5319bba34ffb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60f0e90c335352e3e2f9deac1c18f247bcd791727d751309fe25a41ce2f66d9f816e60e4c7c6bb9db9b3b2f90fffb2bd36a9463dd006e5373338a7944f20a955
|
7
|
+
data.tar.gz: 97cb2d1b0c0cad4616f7e0ab2b8d682fced257c545117b2bb488a66aad73f9a36f479926cb24c8b327e6102855aee9cc97cd75a99b9af519ccd114d718d44d0a
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -28,7 +28,7 @@ Hazard is a very simple dice library for ruby that allows you to :
|
|
28
28
|
Add this line to your application's Gemfile:
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
gem 'hazard', '~> 1.
|
31
|
+
gem 'hazard', '~> 1.3', '>= 1.3.1'
|
32
32
|
```
|
33
33
|
|
34
34
|
And then execute:
|
@@ -195,6 +195,21 @@ Examples :
|
|
195
195
|
>> wt = WeightedTable.from_flat_table( :foo, :foo, :bar ]
|
196
196
|
>> wt.sample
|
197
197
|
# This ensure that you will get 66% foo and 33% bar
|
198
|
+
|
199
|
+
### Weighted tables can also be used with floating points numbers
|
200
|
+
|
201
|
+
>> wt = WeightedTable.new( floating_points: true )
|
202
|
+
>> wt.from_weighted_table( [ <weight1>, <object1> ], [ <weight2>, <object2> ], ... ]
|
203
|
+
# Create a weighted table storing objects according to theire weights
|
204
|
+
|
205
|
+
>> wt.sample
|
206
|
+
# Return weighted random object
|
207
|
+
|
208
|
+
Examples :
|
209
|
+
|
210
|
+
>> wt = WeightedTable.new( floating_points: true )
|
211
|
+
>> wt.from_weighted_table( [ 2.6, :foo ], [ 1.5, :bar ] ]
|
212
|
+
>> wt.sample
|
198
213
|
|
199
214
|
### Saving and loading
|
200
215
|
|
data/hazard.gemspec
CHANGED
data/lib/hazard/version.rb
CHANGED
data/lib/weighted_table.rb
CHANGED
@@ -5,11 +5,14 @@ require 'yaml'
|
|
5
5
|
# @author Cédric ZUGER
|
6
6
|
class WeightedTable
|
7
7
|
|
8
|
-
BASE_WEIGHT =
|
8
|
+
BASE_WEIGHT = 0
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# Initialize a WeightedTable
|
11
|
+
# setting floating_points to true indicate that you will work with floating points rather than integers.
|
12
|
+
def initialize( floating_points: false )
|
13
|
+
@weights = []
|
12
14
|
@max_weight = BASE_WEIGHT
|
15
|
+
@floating_points = floating_points
|
13
16
|
end
|
14
17
|
|
15
18
|
# Load a WeightedTable with data
|
@@ -28,13 +31,23 @@ class WeightedTable
|
|
28
31
|
# @return [WeightedTable] the current WeightedTable
|
29
32
|
def from_weighted_table( table )
|
30
33
|
raise 'Table must contain at least one element' if table.empty?
|
34
|
+
|
35
|
+
# We may call this method many time for an existing object, so we must clear it
|
36
|
+
@weights.clear
|
37
|
+
|
31
38
|
base = BASE_WEIGHT
|
32
|
-
|
33
|
-
|
39
|
+
w = nil
|
40
|
+
|
41
|
+
table.each do |weight, data|
|
42
|
+
w = base + weight
|
43
|
+
@weights << [base, w, data]
|
34
44
|
base = w
|
35
|
-
|
36
|
-
|
37
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
@max_weight = w
|
48
|
+
|
49
|
+
# p @weights, @max_weight
|
50
|
+
|
38
51
|
self
|
39
52
|
end
|
40
53
|
|
@@ -51,16 +64,19 @@ class WeightedTable
|
|
51
64
|
#
|
52
65
|
# @return [Object] a random object given at the building of the table.
|
53
66
|
def sample
|
54
|
-
r = Kernel.rand( 1..@max_weight )
|
55
|
-
keys = @weights.keys.sort
|
56
67
|
|
57
|
-
|
68
|
+
# ... means that @max_weight is excluded
|
69
|
+
if @floating_points
|
70
|
+
r = Kernel.rand( 0.0...@max_weight.to_f )
|
71
|
+
else
|
72
|
+
r = Kernel.rand( 0...@max_weight )
|
73
|
+
end
|
58
74
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
75
|
+
# p r
|
76
|
+
|
77
|
+
@weights.each do |base_weight, max_weight, data|
|
78
|
+
return data if r >= base_weight && r < max_weight
|
79
|
+
end
|
64
80
|
|
65
81
|
raise 'Rand not in key range'
|
66
82
|
end
|
data/test/weighted_table_test.rb
CHANGED
@@ -2,92 +2,162 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class HazardTest < Minitest::Test
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
5
|
+
def test_empty_table
|
6
|
+
assert_raises do
|
7
|
+
WeightedTable.from_weighted_table []
|
8
|
+
end
|
9
|
+
assert_raises do
|
10
|
+
WeightedTable.from_flat_table []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_one_element_table
|
15
|
+
assert_equal :foo, WeightedTable.from_weighted_table( [ [ 3, :foo ] ] ).sample
|
16
|
+
assert_equal :foo, WeightedTable.from_flat_table( [ :foo, :foo, :foo ] ).sample
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_two_element_table
|
20
|
+
Kernel.stubs( :rand ).returns( 0 )
|
21
|
+
assert_raises do
|
22
|
+
WeightedTable.from_weighted_table []
|
23
|
+
end
|
24
|
+
assert_raises do
|
25
|
+
WeightedTable.from_flat_table []
|
26
|
+
end
|
27
|
+
|
28
|
+
Kernel.stubs( :rand ).returns( 0 )
|
29
|
+
assert_equal :foo, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ] ] ).sample
|
30
|
+
assert_equal :foo, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar ] ).sample
|
31
|
+
Kernel.stubs( :rand ).returns( 1 )
|
32
|
+
assert_equal :foo, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ] ] ).sample
|
33
|
+
assert_equal :foo, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar ] ).sample
|
34
|
+
Kernel.stubs( :rand ).returns( 2 )
|
35
|
+
assert_equal :foo, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ] ] ).sample
|
36
|
+
assert_equal :foo, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar ] ).sample
|
37
|
+
Kernel.stubs( :rand ).returns( 3 )
|
38
|
+
assert_equal :bar, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ] ] ).sample
|
39
|
+
assert_equal :bar, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar ] ).sample
|
40
|
+
|
41
|
+
Kernel.stubs( :rand ).returns( 4 )
|
42
|
+
assert_raises 'Rand not in key range' do
|
43
|
+
WeightedTable.from_weighted_table []
|
44
|
+
end
|
45
|
+
assert_raises 'Rand not in key range' do
|
46
|
+
WeightedTable.from_flat_table []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_three_element_table
|
51
|
+
|
52
|
+
Kernel.stubs( :rand ).returns( 0 )
|
53
|
+
assert_equal :foo, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ], [ 1, :foobar ] ] ).sample
|
54
|
+
assert_equal :foo, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar, :foobar ] ).sample
|
55
|
+
|
56
|
+
Kernel.stubs( :rand ).returns( 4 )
|
57
|
+
assert_equal :foobar, WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ], [ 1, :foobar ] ] ).sample
|
58
|
+
assert_equal :foobar, WeightedTable.from_flat_table( [ :foo, :foo, :foo, :bar, :foobar ] ).sample
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_floating_points
|
62
|
+
# 1 -> 4.5 => :foo
|
63
|
+
# 4.5 -> 6 => :bar
|
64
|
+
# 6 -> 7.5 => :foobar
|
65
|
+
wt = WeightedTable.from_weighted_table( [ [ 3.5, :foo ], [ 1.5, :bar ], [ 1.5, :foobar ] ] )
|
66
|
+
|
67
|
+
Kernel.stubs( :rand ).returns( 0.5 )
|
68
|
+
assert_raises 'Rand not in key range' do
|
69
|
+
WeightedTable.from_weighted_table []
|
70
|
+
end
|
71
|
+
|
72
|
+
Kernel.stubs( :rand ).returns( 1 )
|
73
|
+
assert_equal :foo, wt.sample
|
74
|
+
|
75
|
+
Kernel.stubs( :rand ).returns( 1.5 )
|
76
|
+
assert_equal :foo, wt.sample
|
77
|
+
|
78
|
+
Kernel.stubs( :rand ).returns( 4.5 )
|
79
|
+
assert_equal :bar, wt.sample
|
80
|
+
|
81
|
+
Kernel.stubs( :rand ).returns( 4.6 )
|
82
|
+
assert_equal :bar, wt.sample
|
83
|
+
|
84
|
+
Kernel.stubs( :rand ).returns( 6 )
|
85
|
+
assert_equal :foobar, wt.sample
|
86
|
+
|
87
|
+
Kernel.stubs( :rand ).returns( 6.3 )
|
88
|
+
assert_equal :foobar, wt.sample
|
89
|
+
|
90
|
+
Kernel.stubs( :rand ).returns( 7.1 )
|
91
|
+
assert_raises 'Rand not in key range' do
|
92
|
+
WeightedTable.from_weighted_table []
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_floating_point_statistics
|
97
|
+
wt = WeightedTable.new( floating_points: true )
|
98
|
+
|
99
|
+
tests_arrays = [
|
100
|
+
[[ 2.3, :foo ], [ 1.6, :bar ]], [[ 6.4, :foo ], [ 8.2, :bar ]], [[ 35.7, :foo ], [ 678.9, :bar ]],
|
101
|
+
[[ 6.4, :foo ], [ 8.7, :bar ], [ 4.1, :foobar ]], [[ 56, :foo ], [ 4.9, :bar ], [ 665.4, :foobar ]]
|
102
|
+
]
|
103
|
+
|
104
|
+
tests_arrays.each do |t_a|
|
105
|
+
|
106
|
+
wt.from_weighted_table t_a
|
107
|
+
results = Hash[t_a.map{ |_, elem| [ elem, 0 ] }]
|
108
|
+
|
109
|
+
rounds = 100000
|
110
|
+
1.upto(rounds).each do
|
111
|
+
results[ wt.sample ] += 1
|
112
|
+
end
|
113
|
+
|
114
|
+
weights_sum = t_a.map{ |weight, elem| weight }.inject( :+ ).to_f
|
115
|
+
|
116
|
+
t_a.each do |weight, elem|
|
117
|
+
assert_in_delta weight/weights_sum, (results[elem].to_f/rounds), 0.01
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_statistics
|
123
|
+
tests_arrays = [
|
124
|
+
[[ 2, :foo ], [ 1, :bar ]], [[ 6, :foo ], [ 8, :bar ]], [[ 35, :foo ], [ 678, :bar ]],
|
125
|
+
[[ 6, :foo ], [ 8, :bar ], [ 4, :foobar ]], [[ 56, :foo ], [ 4, :bar ], [ 665, :foobar ]]
|
126
|
+
]
|
127
|
+
|
128
|
+
tests_arrays.each do |t_a|
|
129
|
+
wt = WeightedTable.from_weighted_table t_a
|
130
|
+
results = Hash[t_a.map{ |_, elem| [ elem, 0 ] }]
|
131
|
+
|
132
|
+
rounds = 100000
|
133
|
+
|
134
|
+
1.upto(rounds).each do
|
135
|
+
results[ wt.sample ] += 1
|
136
|
+
end
|
137
|
+
|
138
|
+
weights_sum = t_a.map{ |weight, elem| weight }.inject( :+ ).to_f
|
139
|
+
|
140
|
+
t_a.each do |weight, elem|
|
141
|
+
assert_in_delta weight/weights_sum, (results[elem].to_f/rounds), 0.01
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_save_and_load
|
147
|
+
wt = WeightedTable.from_weighted_table [ [ 2, :foo ], [ 1, :bar ] ]
|
148
|
+
wt.to_file( 'wt.yaml' )
|
149
|
+
wt = nil
|
150
|
+
wt = WeightedTable.from_file( 'wt.yaml' )
|
151
|
+
Kernel.stubs( :rand ).returns( 1 )
|
152
|
+
assert_equal :foo, wt.sample
|
153
|
+
assert_equal :foo, wt.sample
|
154
|
+
`rm wt.yaml`
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_out_of_range
|
158
|
+
Kernel.stubs( :rand ).returns( 99 )
|
159
|
+
assert_raises do
|
160
|
+
WeightedTable.from_weighted_table( [ [ 3, :foo ], [ 1, :bar ], [ 1, :foobar ] ] ).sample
|
161
|
+
end
|
162
|
+
end
|
93
163
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hazard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cédric ZUGER
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -73,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '2.0'
|
77
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - ">="
|