conditional_sample 0.1.0 → 1.0.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
  SHA1:
3
- metadata.gz: 66f4bd90cb090564aa82b8fcf2542ee09be6d90e
4
- data.tar.gz: d92cfeb38c8587e14a787c22865dd393b1ca953f
3
+ metadata.gz: b434daa60973a4f9635935c2dbd4c0a8f5fbc326
4
+ data.tar.gz: d006afa5b840f9d101115b497130316b88f8f6dc
5
5
  SHA512:
6
- metadata.gz: 4849a931094f7dc5577632fc630216ae22a6c28dd1f9fff08e7aaf8303f52b10a9f13e1ee5495e2a698bcd98adef440e099b7875f37dae760ace83a0db3ae161
7
- data.tar.gz: 76a8a68232ec62e2a83e52d23708b669e41d7fcfe009a1e6b09689621fdd021e69a8edfeaa84f1f14955dc1eacc82de7544ba0e7ff8d5f2649fa56f7b4134510
6
+ metadata.gz: 2d6b55ef238143c688f68ba3c710d3f1a591f6793fdd2fd0870a8d717c36858a37fc9be5ec572a33fd1c0a86e203258e8e4de9ed82ff27adeee69ed17d49ccc5
7
+ data.tar.gz: 3c649f08e5d88f08af315014eaa58e05d8b1871829ebb63e5e650f32ff9d2c87ea14085e6fd1af70ce52ea9a7826a6c9dd90b5bf1464870473be31f921351029
data/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  by Paul Thompson - nossidge@gmail.com
4
4
 
5
5
  This is a Ruby gem that will patch the Array class with a couple of
6
- nice methods for sampling based on the results of an array of Boolean
7
- procs. Array is sampled using the procs as conditions that each specific
8
- array index element must conform to.
6
+ nice methods for sampling based on the results of an array or hash of
7
+ Boolean procs. Array is sampled using the procs as conditions that each
8
+ specific array index element must conform to.
9
9
 
10
10
  I'm using this primarily for procedural generation, where I have an
11
- array of possible values and a certain sample I need, or order in which
12
- I want the values arranged.
11
+ array of possible values and a certain sample I need to extract, or an
12
+ order in which I want the values arranged.
13
13
 
14
14
  This code was spun off into a gem from my poetry generation project
15
15
  https://github.com/nossidge/poefy
@@ -57,11 +57,16 @@ elements from the input array are appended.
57
57
 
58
58
  This is an array of boolean procs that evaluate true if an element is
59
59
  allowed to be placed in that position.
60
+
60
61
  The arguments for each proc are |arr, elem|
61
62
  * **arr** is a reference to the current array that has been built up
62
63
  through the recursion chain.
63
64
  * **elem** is a reference to the current element being considered.
64
65
 
66
+ This can also be a hash. In that case, the key will correspond to
67
+ the element in the output array. Non-Integer keys are ignored, and
68
+ there is no implicit call to #to_i.
69
+
65
70
 
66
71
  ### The 'seconds' argument
67
72
 
@@ -144,6 +149,43 @@ p shuf_sample # => [2, 5, 4]
144
149
  ```
145
150
 
146
151
 
152
+ ### Condition hash example
153
+
154
+ ```ruby
155
+ # 8 element input array.
156
+ array = [1, 2, 3, 4, 'f', 5, nil, 6]
157
+
158
+ # Conditions hash.
159
+ # * [3] is the largest Integer key, so the resulting
160
+ # array will have four elements.
161
+ # * Keys ['1'] and [nil] aren't Integers, so are ignored.
162
+ # * Key [2] is given the default value of:
163
+ # proc { |arr, elem| true }
164
+ conditions = {
165
+ 1 => proc { |arr, elem| elem.to_i > 1 },
166
+ 3 => proc { |arr, elem| elem.to_i > 5 },
167
+ 0 => proc { |arr, elem| elem == 'f' },
168
+ '1' => proc { |arr, elem| false },
169
+ nil => proc { |arr, elem| false }
170
+ }
171
+
172
+ # These will always return the below output
173
+ # because they are the first values that match.
174
+ permut = array.conditional_permutation(conditions)
175
+ sample = array.conditional_sample(conditions)
176
+ p permut # => ['f', 2, 1, 6, 3, 4, 5, nil]
177
+ p sample # => ['f', 2, 1, 6]
178
+
179
+ # To get a random sample, #shuffle the array first.
180
+ # These results will vary based on the shuffle.
181
+ shuf = array.shuffle
182
+ shuf_permut = shuf.conditional_permutation(conditions)
183
+ shuf_sample = shuf.conditional_sample(conditions)
184
+ p shuf_permut # => ['f', 3, 5, 6, 2, 1, nil, 4]
185
+ p shuf_sample # => ['f', 3, 5, 6]
186
+ ```
187
+
188
+
147
189
  ### Random rhyming lines
148
190
 
149
191
  A really simple way to extract rhyming lines from an input array.
@@ -15,4 +15,26 @@ module ConditionalSample
15
15
  #
16
16
  Array.include ConditionalSample::MixMe
17
17
 
18
+ ##
19
+ # Raise an error if an object does not respond to a specific method.
20
+ #
21
+ def self.method_assert object, method_name
22
+ unless object.respond_to?(method_name)
23
+ raise NoMethodError, "Missing method ##{method_name}"
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Convert a hash to array, with key as index.
29
+ # Fill any missing elements with a default value.
30
+ #
31
+ def self.to_conditions_array input, default = nil
32
+ return input if input.is_a? Array
33
+
34
+ # Get a list of all Integer keys.
35
+ # Use the biggest key, and make an array of that length + 1.
36
+ keys = input.keys.select { |i| i.is_a? Integer }
37
+ keys.max.next.times.map { |i| input[i] || default }
38
+ end
39
+
18
40
  end
@@ -78,7 +78,7 @@ module ConditionalSample
78
78
  break if valid
79
79
  end
80
80
 
81
- output.flatten.compact
81
+ output.flatten
82
82
  end
83
83
 
84
84
  ##
@@ -130,7 +130,7 @@ module ConditionalSample
130
130
  break if valid
131
131
  end
132
132
 
133
- output.flatten.compact
133
+ output.flatten
134
134
  end
135
135
 
136
136
  end
@@ -37,11 +37,16 @@ module ConditionalSample
37
37
  #
38
38
  # possible output => [1, 3, 4, 5, 2]
39
39
  #
40
- # Will not work on arrays that contain nil values.
41
- #
42
40
  def conditional_permutation conditions, seconds = nil
41
+ ConditionalSample::method_assert(self, 'to_a')
42
+
43
+ # Convert the conditions to an array.
44
+ conditions =
45
+ ConditionalSample::to_conditions_array(conditions, proc { true })
46
+
47
+ # Run the recursion, and rescue after a number of seconds.
43
48
  timeout_rescue(seconds, []) do
44
- conditional_permutation_recurse(self.to_a, conditions)
49
+ conditional_permutation_recurse(self.to_a, conditions).tap{ |i| i.pop }
45
50
  end
46
51
  end
47
52
 
@@ -65,11 +70,20 @@ module ConditionalSample
65
70
  #
66
71
  # possible output => [1, 5, 3]
67
72
  #
68
- # Will not work on arrays that contain nil values.
69
- #
70
73
  def conditional_sample conditions, seconds = nil
74
+ ConditionalSample::method_assert(self, 'to_a')
75
+
76
+ # Would always return [] anyway if there are more conditions than
77
+ # inputs, this just avoids running the complex recursion code.
78
+ return [] if conditions.length > self.to_a.length
79
+
80
+ # Convert the conditions to an array.
81
+ conditions =
82
+ ConditionalSample::to_conditions_array(conditions, proc { true })
83
+
84
+ # Run the recursion, and rescue after a number of seconds.
71
85
  timeout_rescue(seconds, []) do
72
- conditional_sample_recurse(self.to_a, conditions)
86
+ conditional_sample_recurse(self.to_a, conditions).tap{ |i| i.pop }
73
87
  end
74
88
  end
75
89
 
@@ -7,8 +7,8 @@ module ConditionalSample
7
7
  # The number of the current version.
8
8
  #
9
9
  def self.version_number
10
- major = 0
11
- minor = 1
10
+ major = 1
11
+ minor = 0
12
12
  tiny = 0
13
13
  pre = nil
14
14
 
@@ -19,6 +19,6 @@ module ConditionalSample
19
19
  # The date of the current version.
20
20
  #
21
21
  def self.version_date
22
- '2017-06-27'
22
+ '2017-06-30'
23
23
  end
24
24
  end
@@ -13,9 +13,9 @@ describe ConditionalSample, "basic behaviour" do
13
13
  let(:numbers) { (1..5).to_a }
14
14
  let(:conditions) {
15
15
  [
16
- proc { |arr, elem| elem < 2},
17
- proc { |arr, elem| elem > 4},
18
- proc { |arr, elem| elem > 1}
16
+ proc { |arr, elem| elem.to_i < 2},
17
+ proc { |arr, elem| elem.to_i > 4},
18
+ proc { |arr, elem| elem.to_i > 1}
19
19
  ]
20
20
  }
21
21
  let(:output_sample) { [1, 5, 2] }
@@ -40,6 +40,81 @@ describe ConditionalSample, "basic behaviour" do
40
40
  expect(result[0]).to be 1
41
41
  expect(result[1]).to be 5
42
42
  end
43
+
44
+ it "should correctly handle nil values" do
45
+ add_nil_values = numbers.dup
46
+ 10.times do
47
+ add_nil_values << nil
48
+ result = add_nil_values.shuffle.conditional_sample(conditions)
49
+ expect(result.count).to be conditions.count
50
+ result = add_nil_values.shuffle.conditional_permutation(conditions)
51
+ expect(result.count).to be add_nil_values.count
52
+ end
53
+ end
54
+
55
+ it "should correctly handle nil values" do
56
+ add_nil_values = numbers.dup
57
+ 10.times do
58
+ add_nil_values << nil
59
+ result = add_nil_values.shuffle.conditional_sample(conditions)
60
+ expect(result.count).to be conditions.count
61
+ result = add_nil_values.shuffle.conditional_permutation(conditions)
62
+ expect(result.count).to be add_nil_values.count
63
+ end
64
+ end
65
+
66
+ it "conditional_permutation should correctly handle nil values" do
67
+ add_nil_values = numbers.dup
68
+ 10.times do
69
+ add_nil_values << nil
70
+ result = add_nil_values.shuffle.conditional_sample(conditions)
71
+ expect(result.count).to be conditions.count
72
+ result = add_nil_values.shuffle.conditional_permutation(conditions)
73
+ expect(result.count).to be add_nil_values.count
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ ################################################################################
80
+
81
+ describe ConditionalSample, "when conditions.length > array.length" do
82
+
83
+ # Input values.
84
+ let(:array) { [1, 3, 4, "f", 5, 2, nil, 6] }
85
+ let(:possible) {
86
+ 100.times.map do
87
+ proc { |arr, elem| true }
88
+ end
89
+ }
90
+ let(:impossible) {
91
+ 100.times.map do
92
+ proc { |arr, elem| elem.to_i > 1 }
93
+ end
94
+ }
95
+
96
+ describe "conditional_sample" do
97
+ it "with procs that are possible, should output []" do
98
+ result = array.conditional_sample(possible)
99
+ expect(result).to eq []
100
+ end
101
+ it "with procs that are impossible, should output []" do
102
+ result = array.conditional_sample(impossible)
103
+ expect(result).to eq []
104
+ end
105
+ end
106
+
107
+ describe "conditional_permutation" do
108
+ it "with procs that are possible, should output the array" do
109
+ result = array.conditional_permutation(possible)
110
+ expect(result).to eq array
111
+ end
112
+ it "with procs that are impossible, should output []" do
113
+ result = array.conditional_permutation(impossible)
114
+ expect(result).to eq []
115
+ end
116
+ end
117
+
43
118
  end
44
119
 
45
120
  ################################################################################
@@ -58,39 +133,49 @@ describe ConditionalSample, "mixin behaviour" do
58
133
  let(:output_sample) { [1, 5, 2] }
59
134
  let(:output_permutation) { [1, 5, 2, 3, 4] }
60
135
 
61
- it "should correctly work with a Struct implementing #to_a" do
136
+ it "should correctly work with a Class implementing #to_a" do
62
137
 
63
- # Attempt to mix in using extend on a Struct instance.
64
- struct = Struct.new(:contents) do
138
+ # Class which includes MixMe and implements #to_a
139
+ class MixerWith
140
+ include ConditionalSample::MixMe
141
+ def initialize value
142
+ @value = value
143
+ end
65
144
  def to_a
66
- contents.to_a
145
+ @value.to_a
67
146
  end
68
- end.new numbers
69
- struct.extend ConditionalSample::MixMe
147
+ end
148
+ mixer = MixerWith.new numbers
70
149
 
71
150
  # Run the methods, and compare results.
72
- result = struct.conditional_sample(conditions)
151
+ result = mixer.conditional_sample(conditions)
73
152
  expect(result).to eq output_sample
74
153
 
75
- result = struct.conditional_permutation(conditions)
154
+ result = mixer.conditional_permutation(conditions)
76
155
  expect(result).to eq output_permutation
77
156
  end
78
157
 
79
- it "should fail on a Struct not implementing #to_a" do
158
+ it "should fail on a Class not implementing #to_a" do
80
159
 
81
- # Attempt to mix in using extend on a Struct instance.
82
- struct = Struct.new(:contents).new numbers
83
- struct.extend ConditionalSample::MixMe
160
+ # Class which includes MixMe but doesn't implement #to_a
161
+ class MixerWithout
162
+ include ConditionalSample::MixMe
163
+ def initialize value
164
+ @value = value
165
+ end
166
+ end
167
+ mixer = MixerWithout.new numbers
84
168
 
85
169
  # Run the methods, and expect that they will fail.
86
170
  expect do
87
- struct.conditional_sample(conditions)
171
+ mixer.conditional_sample(conditions)
88
172
  end.to raise_error NoMethodError
89
173
 
90
174
  expect do
91
- struct.conditional_permutation(conditions)
175
+ mixer.conditional_permutation(conditions)
92
176
  end.to raise_error NoMethodError
93
177
  end
178
+
94
179
  end
95
180
 
96
181
  ################################################################################
@@ -40,6 +40,39 @@ describe ConditionalSample, "examples" do
40
40
 
41
41
  ##############################################################################
42
42
 
43
+ it "should output the correct results: Condition hash example" do
44
+
45
+ # 8 element input array.
46
+ array = [1, 2, 3, 4, 'f', 5, nil, 6]
47
+
48
+ # 4 element conditions hash.
49
+ conditions = {
50
+ 1 => proc { |arr, elem| elem.to_i > 1 },
51
+ 3 => proc { |arr, elem| elem.to_i > 5 },
52
+ 0 => proc { |arr, elem| elem == 'f' },
53
+ '1' => proc { |arr, elem| false },
54
+ nil => proc { |arr, elem| false }
55
+ }
56
+
57
+ # These will always return the below output
58
+ # because they are the first values that match.
59
+ permut = array.conditional_permutation(conditions)
60
+ sample = array.conditional_sample(conditions)
61
+ expect(permut).to eq ['f', 2, 1, 6, 3, 4, 5, nil]
62
+ expect(sample).to eq ['f', 2, 1, 6]
63
+
64
+ # To get a random sample, #shuffle the array first.
65
+ # These results will vary based on the shuffle.
66
+ srand(37)
67
+ shuf = array.shuffle
68
+ shuf_permut = shuf.conditional_permutation(conditions)
69
+ shuf_sample = shuf.conditional_sample(conditions)
70
+ expect(shuf_permut).to eq ['f', 3, 5, 6, 2, 1, nil, 4]
71
+ expect(shuf_sample).to eq ['f', 3, 5, 6]
72
+ end
73
+
74
+ ##############################################################################
75
+
43
76
  it "should output the correct results: Random rhyming lines" do
44
77
 
45
78
  # Use this gem to get the rhyme of the line's final word.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conditional_sample
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-27 00:00:00.000000000 Z
11
+ date: 2017-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler