games_dice 0.3.12 → 0.4.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 +5 -5
- data/.rubocop.yml +15 -0
- data/.travis.yml +7 -10
- data/CHANGELOG.md +6 -0
- data/Gemfile +2 -0
- data/Rakefile +14 -11
- data/ext/games_dice/extconf.rb +4 -22
- data/ext/games_dice/probabilities.c +1 -1
- data/games_dice.gemspec +26 -32
- data/lib/games_dice/bunch.rb +241 -247
- data/lib/games_dice/complex_die.rb +287 -270
- data/lib/games_dice/complex_die_helpers.rb +68 -60
- data/lib/games_dice/constants.rb +10 -10
- data/lib/games_dice/dice.rb +146 -143
- data/lib/games_dice/die.rb +101 -97
- data/lib/games_dice/die_result.rb +193 -189
- data/lib/games_dice/map_rule.rb +72 -70
- data/lib/games_dice/marshal.rb +18 -13
- data/lib/games_dice/parser.rb +219 -218
- data/lib/games_dice/reroll_rule.rb +76 -77
- data/lib/games_dice/version.rb +3 -1
- data/lib/games_dice.rb +19 -19
- data/spec/bunch_spec.rb +399 -420
- data/spec/complex_die_spec.rb +314 -305
- data/spec/dice_spec.rb +33 -34
- data/spec/die_result_spec.rb +162 -169
- data/spec/die_spec.rb +81 -81
- data/spec/helpers.rb +23 -21
- data/spec/map_rule_spec.rb +40 -44
- data/spec/parser_spec.rb +106 -82
- data/spec/probability_spec.rb +530 -526
- data/spec/readme_spec.rb +404 -390
- data/spec/reroll_rule_spec.rb +40 -44
- metadata +39 -28
- data/lib/games_dice/prob_helpers.rb +0 -259
- data/lib/games_dice/probabilities.rb +0 -244
data/spec/reroll_rule_spec.rb
CHANGED
@@ -1,44 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
it
|
8
|
-
GamesDice::RerollRule.new(
|
9
|
-
GamesDice::RerollRule.new(
|
10
|
-
end
|
11
|
-
|
12
|
-
it
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
it
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
rule.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
rule.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'helpers'
|
4
|
+
|
5
|
+
describe GamesDice::RerollRule do
|
6
|
+
describe '#new' do
|
7
|
+
it 'should accept self-consistent operator/value pairs as a trigger' do
|
8
|
+
GamesDice::RerollRule.new(5, :>, :reroll_subtract)
|
9
|
+
GamesDice::RerollRule.new((1..5), :member?, :reroll_replace)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should reject inconsistent operator/value pairs for a trigger' do
|
13
|
+
expect(-> { GamesDice::RerollRule.new(5, :member?, :reroll_subtract) }).to raise_error(ArgumentError)
|
14
|
+
expect(-> { GamesDice::RerollRule.new((1..5), :>, :reroll_replace) }).to raise_error(ArgumentError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should reject bad re-roll types' do
|
18
|
+
expect(-> { GamesDice::RerollRule.new(5, :>, :reroll_again) }).to raise_error(ArgumentError)
|
19
|
+
expect(-> { GamesDice::RerollRule.new((1..5), :member?, 42) }).to raise_error(ArgumentError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#applies?' do
|
24
|
+
it 'should return true if a trigger condition is met' do
|
25
|
+
rule = GamesDice::RerollRule.new(5, :>, :reroll_subtract)
|
26
|
+
expect(rule.applies?(4)).to be true
|
27
|
+
|
28
|
+
rule = GamesDice::RerollRule.new((1..5), :member?, :reroll_subtract)
|
29
|
+
expect(rule.applies?(4)).to be true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should return false if a trigger condition is not met' do
|
33
|
+
rule = GamesDice::RerollRule.new(5, :>, :reroll_subtract)
|
34
|
+
expect(rule.applies?(7)).to be false
|
35
|
+
|
36
|
+
rule = GamesDice::RerollRule.new((1..5), :member?, :reroll_subtract)
|
37
|
+
expect(rule.applies?(6)).to be false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: games_dice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Slater
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: coveralls
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.6.7
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.6.7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.7
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.7.7
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,75 +53,75 @@ dependencies:
|
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: 1.9.1
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: rake-compiler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.8.
|
61
|
+
version: 0.8.3
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.8.
|
68
|
+
version: 0.8.3
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - ">="
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 2.13.0
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 2.13.0
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
84
|
+
name: rubocop
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - ">="
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.
|
89
|
+
version: 1.2.1
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - ">="
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.
|
96
|
+
version: 1.2.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: yard
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.8.
|
103
|
+
version: 0.8.6
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.8.
|
110
|
+
version: 0.8.6
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: redcarpet
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - ">="
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
117
|
+
version: 3.5.1
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
124
|
+
version: 3.5.1
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: parslet
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +146,7 @@ extensions:
|
|
132
146
|
extra_rdoc_files: []
|
133
147
|
files:
|
134
148
|
- ".gitignore"
|
149
|
+
- ".rubocop.yml"
|
135
150
|
- ".travis.yml"
|
136
151
|
- ".yardopts"
|
137
152
|
- CHANGELOG.md
|
@@ -155,8 +170,6 @@ files:
|
|
155
170
|
- lib/games_dice/map_rule.rb
|
156
171
|
- lib/games_dice/marshal.rb
|
157
172
|
- lib/games_dice/parser.rb
|
158
|
-
- lib/games_dice/prob_helpers.rb
|
159
|
-
- lib/games_dice/probabilities.rb
|
160
173
|
- lib/games_dice/reroll_rule.rb
|
161
174
|
- lib/games_dice/version.rb
|
162
175
|
- spec/bunch_spec.rb
|
@@ -175,7 +188,7 @@ homepage: https://github.com/neilslater/games_dice
|
|
175
188
|
licenses:
|
176
189
|
- MIT
|
177
190
|
metadata: {}
|
178
|
-
post_install_message:
|
191
|
+
post_install_message:
|
179
192
|
rdoc_options: []
|
180
193
|
require_paths:
|
181
194
|
- lib
|
@@ -183,16 +196,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
183
196
|
requirements:
|
184
197
|
- - ">="
|
185
198
|
- !ruby/object:Gem::Version
|
186
|
-
version:
|
199
|
+
version: 2.6.0
|
187
200
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
201
|
requirements:
|
189
202
|
- - ">="
|
190
203
|
- !ruby/object:Gem::Version
|
191
204
|
version: '0'
|
192
205
|
requirements: []
|
193
|
-
|
194
|
-
|
195
|
-
signing_key:
|
206
|
+
rubygems_version: 3.2.3
|
207
|
+
signing_key:
|
196
208
|
specification_version: 4
|
197
209
|
summary: Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided
|
198
210
|
dice, take best 3, results of 10 roll again and add on".
|
@@ -209,4 +221,3 @@ test_files:
|
|
209
221
|
- spec/probability_spec.rb
|
210
222
|
- spec/readme_spec.rb
|
211
223
|
- spec/reroll_rule_spec.rb
|
212
|
-
has_rdoc:
|
@@ -1,259 +0,0 @@
|
|
1
|
-
# @!visibility private
|
2
|
-
module GamesDice::ProbabilityValidations
|
3
|
-
|
4
|
-
# @!visibility private
|
5
|
-
# the Array, Offset representation of probabilities.
|
6
|
-
def to_ao
|
7
|
-
[ @probs, @offset ]
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.included(klass)
|
11
|
-
klass.extend ClassMethods
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def check_probs_array probs_array
|
17
|
-
raise TypeError unless probs_array.is_a?( Array )
|
18
|
-
probs_array.map!{ |n| Float(n) }
|
19
|
-
total = probs_array.inject(0.0) do |t,x|
|
20
|
-
if x < 0.0 || x > 1.0
|
21
|
-
raise ArgumentError, "Found probability value #{x} which is not in range 0.0..1.0"
|
22
|
-
end
|
23
|
-
t+x
|
24
|
-
end
|
25
|
-
if (total-1.0).abs > 1e-6
|
26
|
-
raise ArgumentError, "Total probabilities too far from 1.0 for a valid distribution"
|
27
|
-
end
|
28
|
-
probs_array
|
29
|
-
end
|
30
|
-
|
31
|
-
def check_keep_mode kmode
|
32
|
-
raise "Keep mode #{kmode.inspect} not recognised" unless [:keep_best,:keep_worst].member?( kmode )
|
33
|
-
end
|
34
|
-
|
35
|
-
module ClassMethods
|
36
|
-
# Convert hash to array,offset notation
|
37
|
-
def prob_h_to_ao h
|
38
|
-
rmin,rmax = h.keys.minmax
|
39
|
-
o = rmin
|
40
|
-
s = 1 + rmax - rmin
|
41
|
-
raise ArgumentError, "Range of possible results too large" if s > 1000000
|
42
|
-
a = Array.new( s, 0.0 )
|
43
|
-
h.each { |k,v| a[k-rmin] = Float(v) }
|
44
|
-
[a,o]
|
45
|
-
end
|
46
|
-
|
47
|
-
# Convert array,offset notation to hash
|
48
|
-
def prob_ao_to_h a, o
|
49
|
-
h = Hash.new
|
50
|
-
a.each_with_index { |v,i| h[i+o] = v if v > 0.0 }
|
51
|
-
h
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def check_is_gdp *probs
|
57
|
-
probs.each do |prob|
|
58
|
-
unless prob.is_a?( GamesDice::Probabilities )
|
59
|
-
raise TypeError, "parameter is not a GamesDice::Probabilities"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# @!visibility private
|
67
|
-
# This module is a set of related private methods for GamesDice::Probabilities that
|
68
|
-
# calculate how two distributions can be combined.
|
69
|
-
module GamesDice::ProbabilityCalcAddDistributions
|
70
|
-
private
|
71
|
-
|
72
|
-
def calc_combined_extremes m_a, pd_a, m_b, pd_b
|
73
|
-
[ [ :min, :min ], [ :min, :max ], [ :max, :min ], [ :max, :max ] ].map do |pda_meth, pdb_meth|
|
74
|
-
m_a * pd_a.send(pda_meth) + m_b * pd_b.send(pdb_meth)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b
|
79
|
-
new_probs = Array.new( 1 + combined_max - combined_min, 0.0 )
|
80
|
-
probs_a, offset_a = pd_a.to_ao
|
81
|
-
probs_b, offset_b = pd_b.to_ao
|
82
|
-
|
83
|
-
probs_a.each_with_index do |pa,i|
|
84
|
-
probs_b.each_with_index do |pb,j|
|
85
|
-
k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min
|
86
|
-
pc = pa * pb
|
87
|
-
new_probs[ k ] += pc
|
88
|
-
end
|
89
|
-
end
|
90
|
-
GamesDice::Probabilities.new( new_probs, combined_min )
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
# @!visibility private
|
96
|
-
# This module is a set of related private methods for GamesDice::Probabilities that
|
97
|
-
# calculate how a distribution can be combined with itself.
|
98
|
-
module GamesDice::ProbabilityCalcSums
|
99
|
-
|
100
|
-
private
|
101
|
-
|
102
|
-
def repeat_sum_internal( n )
|
103
|
-
pd_power = self
|
104
|
-
pd_result = nil
|
105
|
-
|
106
|
-
use_power = 1
|
107
|
-
loop do
|
108
|
-
if ( use_power & n ) > 0
|
109
|
-
if pd_result
|
110
|
-
pd_result = GamesDice::Probabilities.add_distributions( pd_result, pd_power )
|
111
|
-
else
|
112
|
-
pd_result = pd_power
|
113
|
-
end
|
114
|
-
end
|
115
|
-
use_power = use_power << 1
|
116
|
-
break if use_power > n
|
117
|
-
pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power )
|
118
|
-
end
|
119
|
-
pd_result
|
120
|
-
end
|
121
|
-
|
122
|
-
def repeat_n_sum_k_internal( n, k, kmode )
|
123
|
-
if k >= n
|
124
|
-
return repeat_sum_internal( n )
|
125
|
-
end
|
126
|
-
new_probs = Array.new( @probs.count * k, 0.0 )
|
127
|
-
new_offset = @offset * k
|
128
|
-
d = n - k
|
129
|
-
|
130
|
-
each do | q, p_maybe |
|
131
|
-
repeat_n_sum_k_each_q( q, p_maybe, n, k, kmode, d, new_probs, new_offset )
|
132
|
-
end
|
133
|
-
|
134
|
-
GamesDice::Probabilities.new( new_probs, new_offset )
|
135
|
-
end
|
136
|
-
|
137
|
-
def repeat_n_sum_k_each_q q, p_maybe, n, k, kmode, d, new_probs, new_offset
|
138
|
-
# keep_distributions is array of Probabilities, indexed by number of keepers > q, which is in 0...k
|
139
|
-
keep_distributions = calc_keep_distributions( k, q, kmode )
|
140
|
-
p_table = calc_p_table( q, p_maybe, kmode )
|
141
|
-
(0...k).each do |kn|
|
142
|
-
repeat_n_sum_k_each_q_kn( k, kn, d, new_probs, new_offset, keep_distributions, p_table )
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def repeat_n_sum_k_each_q_kn k, kn, d, new_probs, new_offset, keep_distributions, p_table
|
147
|
-
keepers = [2] * kn + [1] * (k-kn)
|
148
|
-
p_so_far = keepers.inject(1.0) { |p,idx| p * p_table[idx] }
|
149
|
-
return unless p_so_far > 0.0
|
150
|
-
(0..d).each do |dn|
|
151
|
-
repeat_n_sum_k_each_q_kn_dn( keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table )
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def repeat_n_sum_k_each_q_kn_dn keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table
|
156
|
-
discards = [1] * (d-dn) + [0] * dn
|
157
|
-
sequence = keepers + discards
|
158
|
-
p_sequence = discards.inject( p_so_far ) { |p,idx| p * p_table[idx] }
|
159
|
-
return unless p_sequence > 0.0
|
160
|
-
p_sequence *= GamesDice::Combinations.count_variations( sequence )
|
161
|
-
kd = keep_distributions[kn]
|
162
|
-
kd.each { |r,p_r| new_probs[r-new_offset] += p_r * p_sequence }
|
163
|
-
end
|
164
|
-
|
165
|
-
def calc_keep_distributions k, q, kmode
|
166
|
-
kd_probabilities = calc_keep_definite_distributions q, kmode
|
167
|
-
|
168
|
-
keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ]
|
169
|
-
if kd_probabilities && k > 1
|
170
|
-
(1...k).each do |n|
|
171
|
-
extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) )
|
172
|
-
n_probs = kd_probabilities.repeat_sum( n )
|
173
|
-
keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs )
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
keep_distributions
|
178
|
-
end
|
179
|
-
|
180
|
-
def calc_keep_definite_distributions q, kmode
|
181
|
-
kd_probabilities = nil
|
182
|
-
case kmode
|
183
|
-
when :keep_best
|
184
|
-
p_definites = p_gt(q)
|
185
|
-
kd_probabilities = given_ge( q + 1 ) if p_definites > 0.0
|
186
|
-
when :keep_worst
|
187
|
-
p_definites = p_lt(q)
|
188
|
-
kd_probabilities = given_le( q - 1 ) if p_definites > 0.0
|
189
|
-
end
|
190
|
-
kd_probabilities
|
191
|
-
end
|
192
|
-
|
193
|
-
def calc_p_table q, p_maybe, kmode
|
194
|
-
case kmode
|
195
|
-
when :keep_best
|
196
|
-
p_kept = p_gt(q)
|
197
|
-
p_rejected = p_lt(q)
|
198
|
-
when :keep_worst
|
199
|
-
p_kept = p_lt(q)
|
200
|
-
p_rejected = p_gt(q)
|
201
|
-
end
|
202
|
-
[ p_rejected, p_maybe, p_kept ]
|
203
|
-
end
|
204
|
-
|
205
|
-
end
|
206
|
-
|
207
|
-
|
208
|
-
# @!visibility private
|
209
|
-
# Helper module with optimised Ruby for counting variations of arrays, such as those returned by
|
210
|
-
# Array#repeated_combination
|
211
|
-
#
|
212
|
-
# @example How many ways can [3,3,6] be arranged?
|
213
|
-
# GamesDice::Combinations.count_variations( [3,3,6] )
|
214
|
-
# => 3
|
215
|
-
#
|
216
|
-
# @example When prob( a ) and result( a ) are same for any arrangement of Array a
|
217
|
-
# items = [1,2,3,4,5,6]
|
218
|
-
# items.repeated_combination(5).each do |a|
|
219
|
-
# this_result = result( a )
|
220
|
-
# this_prob = prob( a ) * GamesDice::Combinations.count_variations( a )
|
221
|
-
# # Do something useful with this knowledge! E.g. save it to probability array.
|
222
|
-
# end
|
223
|
-
#
|
224
|
-
module GamesDice::Combinations
|
225
|
-
@@variations_cache = {}
|
226
|
-
@@factorial_cache = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
|
227
|
-
|
228
|
-
# Counts variations of an array. A unique variation is an arrangement of the array elements which
|
229
|
-
# is detectably different (using ==) from any other. So [1,1,1] has only 1 unique arrangement,
|
230
|
-
# but [1,2,3] has 6 possibilities.
|
231
|
-
# @param [Array] array List of things that can be arranged
|
232
|
-
# @return [Integer] Number of unique arrangements
|
233
|
-
def self.count_variations array
|
234
|
-
all_count = array.count
|
235
|
-
group_sizes = group_counts( array )
|
236
|
-
cache_key = all_count.to_s + ":" + group_sizes.join(',')
|
237
|
-
@@variations_cache[cache_key] ||= variations_of( all_count, group_sizes )
|
238
|
-
end
|
239
|
-
|
240
|
-
private
|
241
|
-
|
242
|
-
def self.variations_of all_count, groups
|
243
|
-
all_arrangements = factorial( all_count )
|
244
|
-
# The reject is an optimisation to avoid calculating and multplying by factorial(1) (==1)
|
245
|
-
identical_arrangements = groups.reject {|x| x==1 }.inject(1) { |prod,g| prod * factorial(g) }
|
246
|
-
all_arrangements/identical_arrangements
|
247
|
-
end
|
248
|
-
|
249
|
-
# Returns counts of unique items in array e.g. [8,8,8,7,6,6] returns [1,2,3]
|
250
|
-
# Sort is for caching
|
251
|
-
def self.group_counts array
|
252
|
-
array.group_by {|x| x}.values.map {|v| v.count}.sort
|
253
|
-
end
|
254
|
-
|
255
|
-
def self.factorial n
|
256
|
-
# Can start range from 2 because we have pre-cached the result for n=1
|
257
|
-
@@factorial_cache[n] ||= (2..n).inject(:*)
|
258
|
-
end
|
259
|
-
end
|