loaded_dice 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|