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 +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
|