loaded_dice 0.1.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.
- data/LICENSE +23 -0
- data/README +16 -0
- data/lib/loaded_die.rb +87 -0
- metadata +51 -0
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2012 Kaspar Schiess
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person
|
5
|
+
obtaining a copy of this software and associated documentation
|
6
|
+
files (the "Software"), to deal in the Software without
|
7
|
+
restriction, including without limitation the rights to use,
|
8
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following
|
11
|
+
conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
SYNOPSIS
|
2
|
+
|
3
|
+
die = LoadedDie.new(1,1,2,1,1,1)
|
4
|
+
die.roll # => one of {0,1,2,3,4,5}, with 2 being more likely.
|
5
|
+
|
6
|
+
# And just for kicks, here's a 20 sided non loaded die:
|
7
|
+
die = LoadedDie.new(*([1] * 20))
|
8
|
+
die.roll
|
9
|
+
|
10
|
+
DESCRIPTION
|
11
|
+
|
12
|
+
Roll the dice in your favor! A loaded die implementation that allows
|
13
|
+
simulation of dice with any number of faces. Applications might include games,
|
14
|
+
but also simulations, where events have discrete probabilities and are
|
15
|
+
mutually exclusive.
|
16
|
+
|
data/lib/loaded_die.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# A loaded die that takes a probability map as constructor argument and will
|
2
|
+
# generate die rolls every time you call #roll. Die sides are numbered 0-n,
|
3
|
+
# where n is the size of the probability map.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# # A three sided die with probabilities 1/6, 2/6, 3/6
|
7
|
+
# die = LoadedDie.new(1,2,3)
|
8
|
+
# die.roll # => one of {0, 1, 2}
|
9
|
+
#
|
10
|
+
# This is Voses Alias Method from an excellent description of the various
|
11
|
+
# algorithms by Keith Schwarz. It has a setup cost of O(n), a cost for each
|
12
|
+
# roll of O(1) and uses O(n) memory. I recommend reading "Darts, Dice and
|
13
|
+
# Coins: Sampling from a Discrete Distribution" by Keith Schwarz.
|
14
|
+
#
|
15
|
+
class LoadedDie
|
16
|
+
# Returns the number of sides this die has.
|
17
|
+
attr_reader :sides
|
18
|
+
# The original probability map.
|
19
|
+
attr_reader :probabilities
|
20
|
+
attr_reader :normalized_probabilities
|
21
|
+
|
22
|
+
# Sets up a die with the given probabilities. No need to have a probability
|
23
|
+
# map that sums to 1, the code will normalize the numbers and provide you
|
24
|
+
# with #normalized_probabilities.
|
25
|
+
#
|
26
|
+
def initialize(*probabilities)
|
27
|
+
init(*probabilities)
|
28
|
+
end
|
29
|
+
def init(*probabilities) # :nodoc:
|
30
|
+
@probabilities = probabilities
|
31
|
+
@sides = probabilities.size
|
32
|
+
|
33
|
+
@alias = Array.new(sides)
|
34
|
+
@prob = Array.new(sides)
|
35
|
+
|
36
|
+
# The original algorithm calls for probabilities that sum to 1.
|
37
|
+
sum = probabilities.inject(0) { |a,e| a + e }
|
38
|
+
|
39
|
+
# This is not needed for the algorithm, but is a nice thing to have.
|
40
|
+
@normalized_probabilities =
|
41
|
+
probabilities.map { |e| e/Float(sum) }
|
42
|
+
|
43
|
+
# This is what we'll work with.
|
44
|
+
mul_probabilities = probabilities.map { |e| e*sides/Float(sum) }
|
45
|
+
|
46
|
+
# Partition into worklists small and large
|
47
|
+
small, large = mul_probabilities.
|
48
|
+
# generate tuples <p(i), i>
|
49
|
+
zip((0..sides).to_a).
|
50
|
+
# partition into small and large
|
51
|
+
partition { |p,i| p < 1 }
|
52
|
+
|
53
|
+
until large.empty? || small.empty?
|
54
|
+
pl, l = small.pop
|
55
|
+
pg, g = large.pop
|
56
|
+
|
57
|
+
@prob[l] = pl
|
58
|
+
@alias[l] = g
|
59
|
+
|
60
|
+
pg = (pg + pl) - 1
|
61
|
+
tuple = [pg, g]
|
62
|
+
(pg < 1 ? small : large).
|
63
|
+
push tuple
|
64
|
+
end
|
65
|
+
|
66
|
+
# large will mostly not be empty at this point. Small can be non-empty due
|
67
|
+
# to floating point numerical instability.
|
68
|
+
[large, small].each do |list|
|
69
|
+
until list.empty?
|
70
|
+
pg, g = list.pop
|
71
|
+
@prob[g] = 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Rolls the die once and returns a number in the range 0...sides. Numbers
|
77
|
+
# will be distributed relative to the probabilities given in
|
78
|
+
# #normalized_probabilities.
|
79
|
+
#
|
80
|
+
def roll
|
81
|
+
# First roll a die with homogenous distribution:
|
82
|
+
n = rand(sides)
|
83
|
+
# And now a biased coin with prob(head) == @prob[n]
|
84
|
+
return n if rand() < @prob[n]
|
85
|
+
return @alias[n]
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: loaded_dice
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kaspar Schiess
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-26 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: kaspar.schiess@absurd.li
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- README
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README
|
23
|
+
- lib/loaded_die.rb
|
24
|
+
homepage:
|
25
|
+
licenses: []
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options:
|
28
|
+
- --main
|
29
|
+
- README
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.24
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: Simulates a loaded die with n sides. Perfect for non homogenous randomisation
|
50
|
+
needs.
|
51
|
+
test_files: []
|