rational_choice 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c062495abe33854b1fdda0b37fe663d2f53f1036b2d79c6491577fe06b645b90
4
- data.tar.gz: 5155200d59d02441f02c2500adab6676cfe45a294c1319adea2670eedf7cb8cc
3
+ metadata.gz: 7ae25e7a180ad02a6837d0e085639330ad54577f880211ea5ed9b8bdcbfd24ad
4
+ data.tar.gz: 9415891d43f63156400471ab1ff71dc388cc76fa30943e5c2dfaf4337cb07d81
5
5
  SHA512:
6
- metadata.gz: 3de02bef19b681e9467383fd86e9653813201c4a0af91bc9f9efabb60b1c5fd4265ddc93d004aa9bf90cfe66fdae356a02481d084ce014182ce53e3baf2381ea
7
- data.tar.gz: b76b864d7bda067426bcd555def66fecd57a5652dfd12426fb544e6d6ae955e24899def8cb74710e596797f0de25043eb56887336f19d44037f53e9b2053a3cf
6
+ metadata.gz: 598b15197987bf19787f9affafde489cf2fbb8b8b0260b36b16f93d9d23eee53d10548170dc719c4ef49e37d12568a151e2854c187708013bb99a77678b7f0a1
7
+ data.tar.gz: d6ffe67d2879780a6418fc722bc19a9bfd70b9def2fbd83826dccc077b4ebef54f47531400a6c93e35a9ea8565e2bd03edb24e8f4175d413d5568189f6ac9a1d
@@ -1,7 +1,7 @@
1
1
  # Tiny fuzzy-logic gate for making choices based on a continuum of permitted values
2
2
  # as opposed to a hard condition.
3
3
  module RationalChoice
4
- VERSION = '2.0.1'
4
+ VERSION = '2.1.0'
5
5
 
6
6
  # Gets raised when a multidimensional choice has to be made with a wrong number
7
7
  # of values versus the number of dimensions
@@ -16,9 +16,11 @@ module RationalChoice
16
16
  #
17
17
  # @param false_at_or_below[#to_f, #<=>] the lower bound, at or below which the value will be considered false
18
18
  # @param true_at_or_above[#to_f, #<=>] the upper bound, at or above which the value will be considered true
19
- def initialize(false_at_or_below:, true_at_or_above:)
19
+ # @param random[Random] the RNG, defaults to a new Random
20
+ def initialize(false_at_or_below:, true_at_or_above:, random: Random.new)
20
21
  raise DomainError, "Bounds were the same at #{false_at_or_below}" if false_at_or_below == true_at_or_above
21
22
 
23
+ @random = random
22
24
  @lower, @upper = [false_at_or_below, true_at_or_above].sort
23
25
  @flip_sign = [@lower, @upper].sort != [false_at_or_below, true_at_or_above]
24
26
  end
@@ -55,9 +57,7 @@ module RationalChoice
55
57
  delta = @upper.to_f - @lower.to_f
56
58
  v = (value - @lower).to_f
57
59
  t = (v / delta)
58
-
59
- r = Random.new # To override in tests if needed
60
- r.rand < t
60
+ @random.rand < t
61
61
  else
62
62
  # just seen where it is (below or above)
63
63
  value >= @upper
@@ -79,8 +79,13 @@ module RationalChoice
79
79
  # will be first coerced into one (number of truthy evaluations vs. number of falsey evaluations)
80
80
  # and then a true/false value will be deduced from that.
81
81
  class ManyDimensions
82
- def initialize(*dimensions)
82
+ # Initializes a new Dimension to evaluate values
83
+ #
84
+ # @param dimensions[Array<Dimension>] the dimensions to make a choice over
85
+ # @param random[Random] the RNG, defaults to a new Random
86
+ def initialize(*dimensions, random: Random.new)
83
87
  @dimensions = dimensions
88
+ @random = random
84
89
  raise CardinalityError, '%s has no dimensions to evaluate' % inspect if @dimensions.empty?
85
90
  end
86
91
 
@@ -106,7 +111,7 @@ module RationalChoice
106
111
  evaluations = values.zip(@dimensions).map { |(v, d)| d.choose(v) }
107
112
  num_truthy_choices = evaluations.select { |e| e }.length
108
113
 
109
- Dimension.new(false_at_or_below: 0, true_at_or_above: evaluations.length).choose(num_truthy_choices)
114
+ Dimension.new(false_at_or_below: 0, true_at_or_above: evaluations.length, random: @random).choose(num_truthy_choices)
110
115
  end
111
116
  end
112
117
  end
@@ -39,6 +39,14 @@ describe 'RationalChoice::Dimension' do
39
39
  end
40
40
  end
41
41
 
42
+ describe 'with a given Random' do
43
+ it 'creates a predictable sequence of choices' do
44
+ d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1, random: Random.new(42))
45
+ choices = (1..10).map { d.choose(0.5) }
46
+ expect(choices).to eq([true, false, false, false, true, true, true, false, false, false])
47
+ end
48
+ end
49
+
42
50
  describe 'with values between thresholds creates a sensible choice distribution' do
43
51
  it 'for 0.5 on a continuum from 0 to 1' do
44
52
  d = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1)
@@ -22,6 +22,16 @@ describe 'RationalChoice::ManyDimensions' do
22
22
  RationalChoice::ManyDimensions.new(one_to_zero, one_to_zero, one_to_zero)
23
23
  }
24
24
 
25
+ it 'accepts a custom seed and uses it to generate predictable choices' do
26
+ r = Random.new(42)
27
+ d1 = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1, random: r)
28
+ d2 = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1, random: r)
29
+ d3 = RationalChoice::Dimension.new(false_at_or_below: 0, true_at_or_above: 1, random: r)
30
+ multi_d = RationalChoice::ManyDimensions.new(d1, d2, d3, random: r)
31
+ choices = (1..10).map { multi_d.choose(0.5, 0.2, 0.6) }
32
+ expect(choices).to eq([false, true, false, true, true, false, true, true, true, false])
33
+ end
34
+
25
35
  it 'returns "true" when all dimensions are at or above upper bound' do
26
36
  10_000.times { expect(md.choose(1, 1, 1)).to eq(true) }
27
37
  10_000.times { expect(md.choose(2, 2, 2)).to eq(true) }
@@ -35,7 +45,7 @@ describe 'RationalChoice::ManyDimensions' do
35
45
  it 'returns "true" in approximately 50% of the cases when all the values are at 0.5' do
36
46
  truthy = 0
37
47
  10_000.times { truthy += 1 if md.choose(0.5, 0.5, 0.5) }
38
- expect(truthy).to be_within(100).of(10_000 / 2)
48
+ expect(truthy).to be_within(200).of(10_000 / 2)
39
49
  end
40
50
 
41
51
  it 'returns "true" in approximately 10% of the cases when all the values are at 0.1' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rational_choice
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov