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.
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: []