loaded_dice 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +23 -0
  2. data/README +16 -0
  3. data/lib/loaded_die.rb +87 -0
  4. 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
+
@@ -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: []