conditional_sample 0.1.0 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +47 -5
- data/lib/conditional_sample.rb +22 -0
- data/lib/conditional_sample/private.rb +2 -2
- data/lib/conditional_sample/public.rb +20 -6
- data/lib/conditional_sample/version.rb +3 -3
- data/spec/conditional_sample_spec.rb +102 -17
- data/spec/examples_spec.rb +33 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b434daa60973a4f9635935c2dbd4c0a8f5fbc326
|
4
|
+
data.tar.gz: d006afa5b840f9d101115b497130316b88f8f6dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
7
|
-
procs. Array is sampled using the procs as conditions that each
|
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
|
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.
|
data/lib/conditional_sample.rb
CHANGED
@@ -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
|
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
|
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 =
|
11
|
-
minor =
|
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-
|
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
|
136
|
+
it "should correctly work with a Class implementing #to_a" do
|
62
137
|
|
63
|
-
#
|
64
|
-
|
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
|
-
|
145
|
+
@value.to_a
|
67
146
|
end
|
68
|
-
end
|
69
|
-
|
147
|
+
end
|
148
|
+
mixer = MixerWith.new numbers
|
70
149
|
|
71
150
|
# Run the methods, and compare results.
|
72
|
-
result =
|
151
|
+
result = mixer.conditional_sample(conditions)
|
73
152
|
expect(result).to eq output_sample
|
74
153
|
|
75
|
-
result =
|
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
|
158
|
+
it "should fail on a Class not implementing #to_a" do
|
80
159
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
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
|
-
|
171
|
+
mixer.conditional_sample(conditions)
|
88
172
|
end.to raise_error NoMethodError
|
89
173
|
|
90
174
|
expect do
|
91
|
-
|
175
|
+
mixer.conditional_permutation(conditions)
|
92
176
|
end.to raise_error NoMethodError
|
93
177
|
end
|
178
|
+
|
94
179
|
end
|
95
180
|
|
96
181
|
################################################################################
|
data/spec/examples_spec.rb
CHANGED
@@ -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:
|
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-
|
11
|
+
date: 2017-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|