chance 0.5.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/.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