chance 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ .bundle/
3
+ Gemfile.lock
4
+ pkg/*
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright (c) 2008 Patrick Ewing <patrick.henry.ewing@gmail.com> and Marcel Molina Jr. <marcel@vernix.org>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in the
6
+ # Software without restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8
+ # Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18
+ # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ group :default do
4
+ gem 'rake'
5
+ end
6
+
7
+ group :test do
8
+ gem 'rspec', ">= 1.4.2", :require => 'rspec'
9
+ end
data/README ADDED
@@ -0,0 +1,27 @@
1
+ = Chance
2
+
3
+ Chance is a little Ruby library for expressing uncertainty in your code. Maybe you always wanted to program with probability?
4
+
5
+ The idea originated with Numeric#percent and Kernel#maybe, which Marcel Molina posted to Projectionist, a tumblelog. This led to various snippets for executing code in a fuzzier way than usual. You get such handy, wishy-washy methods as:
6
+
7
+ Date#at_some_point rather than Date#midnight
8
+ Array#pick(percentage) rather than iterating over every element
9
+
10
+ "maybe" is a Kernel method that randomly evaluates to true or false when it is called.
11
+ @bob.lucky_winner? = maybe
12
+ # => true
13
+ @chauncey.lucky_winner? = maybe
14
+ # => false
15
+
16
+ When supplied with a block, it will call it. Or not. Half of the time it just returns nil. For example
17
+
18
+ maybe {rotate_logs}
19
+
20
+ By default, maybe is 50/50. You can also use "probably", "rarely" and "almost_never", or just create your own Chance object like so:
21
+
22
+ 30.percent.chance.of { "rain" }
23
+
24
+ == Running examples ==
25
+ If you have the bundler gem installed, just run
26
+
27
+ bundle exec rake
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake/testtask'
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc "Run all examples"
7
+ RSpec::Core::RakeTask.new(:lib) do |spec|
8
+ rules_engine_root = File.expand_path(File.dirname(__FILE__))
9
+ spec.pattern = rules_engine_root + '/spec/lib/*_spec.rb'
10
+ spec.rspec_opts = ["--color", "--format", "documentation"]
11
+ # spec.ruby_opts="-w"
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new(:examples) do |spec|
15
+ rules_engine_root = File.expand_path(File.dirname(__FILE__))
16
+ spec.pattern = rules_engine_root + '/spec/examples/*_spec.rb'
17
+ spec.rspec_opts = ["--color", "--format", "documentation"]
18
+ # spec.ruby_opts="-w"
19
+ end
20
+
21
+ task :default => [:lib, :examples]
data/chance.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "chance/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "chance"
7
+ s.version = Chance::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Patrick Ewing"]
10
+ s.email = ["patrick.henry.ewing@gmail.com"]
11
+ s.homepage = "https://github.com/hoverbird/chance"
12
+ s.summary = "Programming with probability. YAGNI certitude."
13
+ s.description = "Chance is a little Ruby library for expressing uncertainty in your code. Maybe you always wanted to program with probability?"
14
+
15
+ s.rubyforge_project = "chance"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+ end
data/examples/dice.rb ADDED
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../chance")
2
+
3
+ class Die
4
+ attr_reader :sides, :odds
5
+ def initialize(sides)
6
+ @sides = sides
7
+ @odds = ((1.0 / sides) * 100 ).percent.chance
8
+ end
9
+
10
+ def self.roll(sides)
11
+ new(sides).odds.happens?
12
+ end
13
+
14
+ def roll
15
+ rand(sides).to_i + 1
16
+ end
17
+
18
+ def *(other_die)
19
+ odds * other_die.odds
20
+ end
21
+
22
+ def to_s
23
+ odds.to_f
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../chance")
2
+
3
+ class SchroedingerCat
4
+ def initialize
5
+ @alive = nil
6
+ end
7
+ def alive?
8
+ @alive ||= maybe
9
+ end
10
+ end
11
+
12
+ # @zombie_cat = SchroedingerCat.new
13
+ # puts "The cat is #{ "undead" if @zombie_cat.instance_variable_get(:@alive).nil? } inside the box. We just don't know its state. Let's check."
14
+ # puts "Oh em gee. The cat is #{@zombie_cat.alive ? 'alive' : 'dead' }!!"
15
+ # puts "I wonder if itwas #{@zombie_cat.alive ? 'frisky and impatient' : 'slowly decomposing' } this whole time."
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../chance")
2
+
3
+ def rain
4
+ "'|'|'|'|'|'|'|'"
5
+ end
6
+
7
+ def snow
8
+ ".*.*.*.*.*.*.*."
9
+ end
10
+ #
11
+ # 24.times do |hour|
12
+ # weather = [ 60.percent.chance.of {rain}, 10.percent.chance.of {snow} ]
13
+ # puts [ hour, weather ].join(' ')
14
+ # endfdf
data/lib/chance.rb ADDED
@@ -0,0 +1,2 @@
1
+ chance_lib_dir = File.expand_path(File.dirname(__FILE__)) + "/chance/"
2
+ ['core_extensions', 'percentage', 'chance', 'chance_case'].each {|filename| require chance_lib_dir + filename}
@@ -0,0 +1,67 @@
1
+ class Chance
2
+ attr_reader :odds, :happens, :event
3
+ alias :happens? :happens
4
+ include Comparable
5
+
6
+ def self.case(*chances)
7
+ raise "Chances don't add to 100" unless chances.inject(0) {|sum, chance| sum + chance.to_f } == 100
8
+ ranges = []
9
+ chances = chances.sort_by{|c| c.to_f}
10
+ chances.each_with_index do |chance, i|
11
+ chance = chance.to_f
12
+ range =
13
+ if i == 0
14
+ 0..chance
15
+ elsif i == chances.size - 1
16
+ @last_chance..100
17
+ elsif @last_chance
18
+ @last_chance..chance
19
+ end
20
+ @last_chance = range.begin
21
+ ranges << range
22
+ end
23
+ num = Kernel.rand(100)
24
+ ranges.each_with_index do |r, i|
25
+ if r.include? num
26
+ return chances[i].event.call
27
+ end
28
+ end
29
+ end
30
+
31
+ def initialize(percent)
32
+ @odds = percent
33
+ @happens = @odds.to_f > Kernel.rand(100)
34
+ end
35
+
36
+ def of(&block)
37
+ yield if happens?
38
+ end
39
+
40
+ def will(&block)
41
+ @event = block
42
+ self
43
+ end
44
+
45
+ def to_f
46
+ odds.to_f
47
+ end
48
+ alias :value :to_f
49
+
50
+ def to_s
51
+ "A #{odds.to_f} percent chance"
52
+ end
53
+
54
+ def *(other_chance)
55
+ Chance.new(self.odds.of(chance.odds.to_f))
56
+ end
57
+
58
+ def <=>(other_chance)
59
+ odds.to_f <=> other_chance.to_f
60
+ end
61
+
62
+ def identical(other_chance)
63
+ self == other_chance && self.happens? == other.happens?
64
+ end
65
+ alias :identical? :identical
66
+
67
+ end
@@ -0,0 +1,19 @@
1
+ class ChanceCase
2
+ attr_reader :outcome
3
+
4
+ def self.test(&block)
5
+ puts 'Here goes:'
6
+ case block.arity
7
+ when 0
8
+ yield
9
+ when 1
10
+ yield 'one'
11
+ when 2
12
+ yield 'one', 'two'
13
+ when 3
14
+ yield 'one', 'two', 'three'
15
+ end
16
+ puts 'Done!'
17
+ end
18
+
19
+ end
@@ -0,0 +1,65 @@
1
+ module Kernel
2
+
3
+ def maybe(percent = 50.percent, &block)
4
+ if block_given?
5
+ percent.chance.of &block
6
+ else
7
+ percent.chance.happens?
8
+ end
9
+ end
10
+
11
+ end
12
+
13
+ class Numeric
14
+ def percent
15
+ Percentage.new(self)
16
+ end
17
+
18
+ def reduce_by(percentage = 50.percent, &block)
19
+ block ||= Proc.new {|number| number.small?}
20
+ return self if block.call(self)
21
+ reduced_to = self
22
+ until block.call(reduced_to)
23
+ reduced_to = reduced_to.reduce_to(percentage)
24
+ end
25
+ Integer(reduced_to)
26
+ end
27
+ alias_method :reduce, :reduce_by
28
+ end
29
+
30
+ class String
31
+ def odds
32
+ raise ArgumentError.new "You must express odds like 2:1 or 2-1" unless match /^[0-9]+(:|-)[0-9]+$/
33
+ first, second = *split($1).map {|string| string.to_f}
34
+ Percentage.new((second / first) * 100)
35
+ end
36
+ end
37
+ #
38
+ # class Date
39
+ # def at_some_point
40
+ # (at_midnight..tomorrow.at_midnight).to_a.rand
41
+ # end
42
+ # alias :whenever :at_some_point
43
+ # end
44
+
45
+ class Array
46
+ def random
47
+ self[rand(length)]
48
+ end
49
+
50
+ def pick(percent)
51
+ picks, percentage = [], percent.of(length).round
52
+ while picks.length < percentage
53
+ picks << random
54
+ picks.uniq!
55
+ end
56
+ picks
57
+ end
58
+
59
+ def pick_about(percentage)
60
+ select do |element|
61
+ percentage.chance.happens?
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,35 @@
1
+ class Percentage
2
+ include Comparable
3
+
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def of(number_or_percentage)
11
+ number_or_percentage * (value / 100.0)
12
+ end
13
+
14
+ def chance
15
+ Chance.new self
16
+ end
17
+
18
+ def to_f
19
+ @value.to_f
20
+ end
21
+
22
+ def to_s
23
+ to_f.to_s
24
+ end
25
+
26
+ def <=>(other)
27
+ value <=> other.value
28
+ end
29
+
30
+ def *(other)
31
+ other_value = other.kind_of?(Numeric) ? other : other.value
32
+ Percentage.new value * other_value
33
+ end
34
+
35
+ end
@@ -0,0 +1,3 @@
1
+ module Chance
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Die do
4
+ it "outputs a result when rolled for a given number of sides" do
5
+ @d6 = Die.new(6)
6
+ (1..6).should include(@d6.roll)
7
+ end
8
+ end
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Chance do
4
+ before do
5
+ @chance = 50.percent.chance
6
+ @second_chance = 50.percent.chance
7
+ @good_chance = 90.percent.chance
8
+ @fat_chance = 10.percent.chance # include Sarcasm
9
+ end
10
+
11
+ it "can be created from a Percentage" do
12
+ Percentage.new(20).chance.should be_kind_of Chance
13
+ end
14
+
15
+ it "has #odds expressed as a Percentage" do
16
+ @chance.odds.should be_kind_of Percentage
17
+ end
18
+
19
+ it "should be comparable with another Chance" do
20
+ @chance.should be > @fat_chance
21
+ @chance.should be == @second_chance
22
+ @chance.should be < @good_chance
23
+ end
24
+
25
+ context "case statement" do
26
+ it "renders a single outcome" do
27
+ outcome = Chance.case(
28
+ 70.percent.chance.will {'snow'},
29
+ 20.percent.chance.will {'sleet'},
30
+ 8.percent.chance.will {'sun'},
31
+ 2.percent.chance.will {'knives'}
32
+ )
33
+ %w(sun sleet snow knives).should include(outcome)
34
+ end
35
+
36
+ it "generally evaluates to the expected outcome with stacked odds" do
37
+ outcome = Chance.case(
38
+ 0.01.percent.chance.will {'rain'},
39
+ 99.99.percent.chance.will {'sleet'}
40
+ )
41
+ outcome.should == 'sleet'
42
+ end
43
+
44
+ it "only fires a single case block" do
45
+ @count = 0
46
+ outcome = Chance.case(
47
+ 50.percent.chance.will {@count += 1},
48
+ 50.percent.chance.will {@count += 1}
49
+ )
50
+ @count.should be 1
51
+ end
52
+
53
+ it "generally follows expected probabilities" do
54
+ @heads, @tails = 0, 0
55
+ 10_000.times do
56
+ Chance.case(
57
+ 50.percent.chance.will {@tails += 1},
58
+ 50.percent.chance.will {@heads += 1}
59
+ )
60
+ end
61
+ 10_000.should == @heads + @tails
62
+ (10_000 / 2 - @heads).abs.should be < 200
63
+ end
64
+
65
+ it "should raise if odds add to less than 100" do
66
+ lambda {
67
+ Chance.case(
68
+ 10.percent.chance.will {'rain'},
69
+ 20.percent.chance.will {'sleet'}
70
+ )
71
+ }.should raise_error
72
+ end
73
+
74
+ it "should raise if odds add to more than 100" do
75
+ lambda {
76
+ Chance.case(
77
+ 10.percent.chance.will {'rain'},
78
+ 20.percent.chance.will {'sleet'},
79
+ 90.percent.chance.will {'sleet'}
80
+ )
81
+ }.should raise_error
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Extensions to core classes;" do
4
+
5
+ context "Kernel" do
6
+ describe "maybe" do
7
+ it "returns true or false when called with no args" do
8
+ [true, false].should include maybe
9
+ end
10
+
11
+ it "executes a block of code half the time it's passed in" do
12
+ @times_called = 0
13
+ 1000.times do
14
+ maybe do
15
+ @times_called += 1
16
+ end
17
+ end
18
+
19
+ @times_called.should be > 1, "Odds are 1 in 1000 this fails"
20
+ @times_called.should be < 1000, "Odds are 1 in 1000 this fails"
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ context "Numeric" do
27
+ it "adds a #percent method to Numerics, returning Percentage" do
28
+ 20.percent.should == Percentage.new(20)
29
+ 20.0.percent.should == Percentage.new(20)
30
+ end
31
+
32
+ it "percentages work as expected" do
33
+ 50.percent.of(20).should == 10
34
+ end
35
+
36
+ end
37
+
38
+ context "String" do
39
+ describe "#odds" do
40
+ it "splits a string on :, returning a Percentage" do
41
+ "100:1".odds.should == 1.percent
42
+ end
43
+
44
+ it "splits a string on -, returning a Percentage" do
45
+ "2-1".odds.should == 50.percent
46
+ end
47
+
48
+ it "raises an ArgumentError when not correctly delimited" do
49
+ message = /You must express odds like 2:1 or 2-1/
50
+ lambda {"2 to 1".odds}.should raise_error(ArgumentError, message)
51
+ lambda {"2,1".odds}.should raise_error(ArgumentError, message)
52
+ lambda {"2:1:2".odds}.should raise_error(ArgumentError, message)
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ context "Array" do
59
+ describe "#random" do
60
+ it "returns a randomly selected element" do
61
+ a = [1,2,3]
62
+ a.should include(a.random)
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Percentage do
4
+ it "can be created from an Integer" do
5
+ 20.should == Percentage.new(20).value
6
+ end
7
+
8
+ it "can be created from a Float" do
9
+ 20.0.should == Percentage.new(20.0).value
10
+ end
11
+
12
+ it "is comparable to other Percentages" do
13
+ Percentage.new(20).should == Percentage.new(20)
14
+ Percentage.new(10).should be < Percentage.new(15)
15
+ Percentage.new(15).should be > Percentage.new(10)
16
+ end
17
+
18
+ it "can get a Percentage of a number" do
19
+ result = 50.percent.of 50
20
+ result.should == 25
21
+ end
22
+
23
+ it "can get a Percentage of a Percentage" do
24
+ result = 25.percent.of 90.percent
25
+ result.should == 22.5.percent
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler.require(:default, :examples)
3
+
4
+ require 'rspec'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib', 'chance'))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'examples'))
9
+
10
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),'..', 'lib', 'chance', '**', '*.rb'))].each {|f| require f }
11
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),'..', 'examples', '**', '*.rb'))].each {|f| require f }
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chance
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Patrick Ewing
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-07-13 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Chance is a little Ruby library for expressing uncertainty in your code. Maybe you always wanted to program with probability?
22
+ email:
23
+ - patrick.henry.ewing@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - COPYING
33
+ - Gemfile
34
+ - README
35
+ - Rakefile
36
+ - chance.gemspec
37
+ - examples/dice.rb
38
+ - examples/schroedinger.rb
39
+ - examples/weather.rb
40
+ - lib/chance.rb
41
+ - lib/chance/chance.rb
42
+ - lib/chance/chance_case.rb
43
+ - lib/chance/core_extensions.rb
44
+ - lib/chance/percentage.rb
45
+ - lib/chance/version.rb
46
+ - spec/examples/dice_spec.rb
47
+ - spec/lib/chance_spec.rb
48
+ - spec/lib/core_extensions_spec.rb
49
+ - spec/lib/percentage_spec.rb
50
+ - spec/spec_helper.rb
51
+ has_rdoc: true
52
+ homepage: https://github.com/hoverbird/chance
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project: chance
77
+ rubygems_version: 1.3.6
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Programming with probability. YAGNI certitude.
81
+ test_files:
82
+ - spec/examples/dice_spec.rb
83
+ - spec/lib/chance_spec.rb
84
+ - spec/lib/core_extensions_spec.rb
85
+ - spec/lib/percentage_spec.rb
86
+ - spec/spec_helper.rb