radagen 0.3.3
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 +7 -0
- data/.gitignore +120 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/radagen/generator.rb +76 -0
- data/lib/radagen/version.rb +3 -0
- data/lib/radagen.rb +768 -0
- data/radagen.gemspec +26 -0
- metadata +114 -0
data/lib/radagen.rb
ADDED
@@ -0,0 +1,768 @@
|
|
1
|
+
require 'radagen/version'
|
2
|
+
require 'radagen/generator'
|
3
|
+
|
4
|
+
module Radagen
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Predicate to check if obj is an instance of Gen::Generator
|
8
|
+
#
|
9
|
+
# @param obj [Object] object to be checked
|
10
|
+
# @return [Boolean]
|
11
|
+
#
|
12
|
+
def gen?(obj)
|
13
|
+
obj.class == Radagen::Generator
|
14
|
+
end
|
15
|
+
|
16
|
+
# Creates a generator that will choose (inclusive)
|
17
|
+
# at random a number between lower and upper bounds.
|
18
|
+
# The sampling is *with* replacement.
|
19
|
+
#
|
20
|
+
# @note This is a low level generator and is used to build up most of the generator abstractions
|
21
|
+
# @overload choose(Fixnum, Fixnum)
|
22
|
+
# @param lower [Fixnum] set the lower bound (inclusive)
|
23
|
+
# @param upper [Fixnum] set the upper bound (inclusive)
|
24
|
+
# @return [Radagen::Generator]
|
25
|
+
#
|
26
|
+
# @overload choose(Float, Float)
|
27
|
+
# @param lower [Float] set the lower bound (inclusive)
|
28
|
+
# @param upper [Float] set the upper bound (inclusive)
|
29
|
+
# @return [Radagen::Generator]
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# choose(1, 5).sample(5) #=> [5, 1, 3, 4, 5]
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# choose(0.0, 1.0).sample(5) #=> [0.7892616716655776, 0.9918259286064951, 0.8583139574435731, 0.9567596959947294, 0.3024080166537295]
|
36
|
+
#
|
37
|
+
def choose(lower, upper)
|
38
|
+
raise RangeError.new, 'upper needs to be greater than or equal to lower value' unless upper >= lower
|
39
|
+
RG.new { |prng, _| prng.rand(lower..upper) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a generator that 'exposes' the *size*
|
43
|
+
# value passed to the generator. *block* is a
|
44
|
+
# Proc that takes a fixnum and returns a generator.
|
45
|
+
# This can be used to *see*, manipulate or wrap
|
46
|
+
# another generator with the passed in size.
|
47
|
+
#
|
48
|
+
# @param block [Proc] takes a size returning a generator
|
49
|
+
# @return [Radagen::Generator]
|
50
|
+
#
|
51
|
+
# @example Cube the size passed to a generator
|
52
|
+
# sized do |size|
|
53
|
+
# cube_size = size ** 3
|
54
|
+
# choose(1, cube_size)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def sized(&block)
|
58
|
+
RG.new do |prng, size|
|
59
|
+
sized_gen = block.call(size)
|
60
|
+
sized_gen.call(prng, size)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a generator that allows you to pin the
|
65
|
+
# *size* of a generator to specified value. Think
|
66
|
+
# of the *size* as being the upper bound the generator's
|
67
|
+
# size can be.
|
68
|
+
#
|
69
|
+
# @param gen [Radagen::Generator] generator to set the size on
|
70
|
+
# @param size [Fixnum] actual size to be set
|
71
|
+
# @return [Radagen::Generator]
|
72
|
+
#
|
73
|
+
# @example Set the size of a integer generator
|
74
|
+
# resize(fixnum, 500).sample(3) #=> [484, -326, -234]
|
75
|
+
#
|
76
|
+
# @example Without resize
|
77
|
+
# fixnum.sample(3) #=> [0, 1, 1]
|
78
|
+
#
|
79
|
+
def resize(gen, size)
|
80
|
+
RG.new do |prng, _|
|
81
|
+
gen.call(prng, size)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Creates a generator that allows you to modify the
|
86
|
+
# *size* parameter of the passed in generator with
|
87
|
+
# the block. The block accepts the size and allows you
|
88
|
+
# to change the size at different rates other than linear.
|
89
|
+
#
|
90
|
+
# @see sized
|
91
|
+
# @note This is intended to be used for generators that accept a size. This is partly a convenience function, as you can accomplish much the same with *sized*.
|
92
|
+
# @param gen [Radagen::Generator] generator that will be *resized* by the scaled size
|
93
|
+
# @param block [Proc] proc that will accept transform and return the size parameter
|
94
|
+
# @return [Radagen::Generator]
|
95
|
+
#
|
96
|
+
# @example Cube size parameter
|
97
|
+
# scaled_nums = scale(string_numeric) do |size|
|
98
|
+
# size ** 3
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# scaled_nums.sample(3) #=> ["", "5", "69918"]
|
102
|
+
#
|
103
|
+
def scale(gen, &block)
|
104
|
+
sized do |size|
|
105
|
+
_size = block.call(size)
|
106
|
+
resize(gen, _size)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Takes a generator and a Proc. The Proc is passed a value
|
111
|
+
# taken from calling the generator. You then are free to
|
112
|
+
# transform the value before it is returned.
|
113
|
+
#
|
114
|
+
# @note A value (not a generator) needs to be returned from the Proc.
|
115
|
+
# @param gen [Radagen::Generator] generator that produces values passed to Proc
|
116
|
+
# @param block [Proc] proc that is intended to manipulate value and return it
|
117
|
+
# @return [Radagen::Generator]
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# fmap(fixnum) { |i| [i, i ** i] }.sample(4) #=> [[0, 1], [-1, -1], [2, 4], [-1, -1]]
|
121
|
+
#
|
122
|
+
def fmap(gen, &block)
|
123
|
+
RG.new do |prng, size|
|
124
|
+
block.call(gen.call(prng, size))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Takes a generator and a Proc. The Proc is passed a value
|
129
|
+
# taken from calling the generator. You then can use that
|
130
|
+
# value as an input to the generator returned.
|
131
|
+
#
|
132
|
+
# @note The Proc has to return a generator.
|
133
|
+
# @param gen [Radagen::Generator] generator that produces value passed to Proc
|
134
|
+
# @param block [Proc] takes a value from generator and returns a new generator using that value
|
135
|
+
# @return [Radagen::Generator]
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# bind_gen = bind(fixnum) do |i|
|
139
|
+
# array(string_numeric, {max: i})
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# bind_gen.sample(5) #=> [[], ["1"], ["86"], [], ["64", "25", ""]]
|
143
|
+
#
|
144
|
+
def bind(gen, &block)
|
145
|
+
RG.new do |prng, size|
|
146
|
+
inner_gen = block.call(gen.call(prng, size))
|
147
|
+
inner_gen.call(prng, size)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Takes n generators returning an n-tuple of realized
|
152
|
+
# values from generators in arity order.
|
153
|
+
#
|
154
|
+
# @overload tuple(generator, generator, ...)
|
155
|
+
# @param gens [Radagen::Generator] generators
|
156
|
+
# @return [Radagen::Generator]
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# tuple(string_alphanumeric, uuid, boolean).sample(5) #=> [["", "6e63d2cc-82bf-4b34-b326-e5befeb30309", false], ["A", "2847889f-f93d-4239-86f5-c6411c639554", true], ["", "d04e894f-b35b-4edc-a214-abf76d9c1b60", true], ["05", "7e3ff332-8e35-44ca-905c-8e0538fb14f8", false], ["GR", "3f6c0be8-6b16-4b2b-9510-922961ff0f7a", true]]
|
160
|
+
#
|
161
|
+
def tuple(*gens)
|
162
|
+
raise ArgumentError.new 'all arguments need to be generators' unless gens.all? { |g| gen? g }
|
163
|
+
|
164
|
+
RG.new do |prng, size|
|
165
|
+
gens.map do |gen|
|
166
|
+
gen.call(prng, size)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Creates a generator that when called returns a varying
|
172
|
+
# length Array with values realized from the passed in
|
173
|
+
# generator. Excepts an options Hash that can contain the
|
174
|
+
# keys :min and :max. These will define the minimum and
|
175
|
+
# maximum amount of values in the realized Array.
|
176
|
+
#
|
177
|
+
# @note If you provide a *:min* value then it is good practice to also provide a *:max* value as you can't be sure that the *size* passed to the generator will be greater or equal to :min.
|
178
|
+
# @param gen [Radagen::Generator] generator that produces values in the Array
|
179
|
+
# @param opts [Hash] the options hash to provide extra context to the generator
|
180
|
+
# @option opts [Fixnum] :min (0) minimum number of values in a generated Array
|
181
|
+
# @option opts [Fixnum] :max (*size*) maximum number of values in a generated Array
|
182
|
+
# @return [Radagen::Generator]
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# array(fixnum).sample(4) #=> [[], [-1], [0, -2], [-2, -1]]
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# array(fixnum, max: 5).sample(4) #=> [[0], [0, 0, -1, 1], [0, 0, -2, 1], [3, 2, -2, -1, 0]]
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# array(fixnum, min: 4, max: 7).sample(4) #=> [[0, 0, 0, 0, 0], [-1, 0, -1, 1, -1], [-2, -2, -1, 2, 0], [0, 2, 3, -1, -2, -2, 3]]
|
192
|
+
#
|
193
|
+
def array(gen, opts={})
|
194
|
+
size_gen = sized do |size|
|
195
|
+
min, max = {min: 0, max: size}.merge(opts).values_at(:min, :max)
|
196
|
+
raise RangeError.new, "max value (#{max}) needs to be larger than or equal to min value (#{min}), provide a max value larger than min." unless max >= min
|
197
|
+
choose(min, max)
|
198
|
+
end
|
199
|
+
|
200
|
+
bind(size_gen) do |_size|
|
201
|
+
gens = (0..._size).map { gen }
|
202
|
+
tuple(*gens)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Creates a generator that when called returns a varying
|
207
|
+
# length Set with values realized from the passed in
|
208
|
+
# generator. Excepts the same values and provides similar
|
209
|
+
# behavior of Array options.
|
210
|
+
#
|
211
|
+
# @see array
|
212
|
+
# @note If the provided generator generates two of the same values then the set will only contain a single representation of that value.
|
213
|
+
# @param gen [Radagen::Generator] generator that produces values in the Set
|
214
|
+
# @param opts [Hash] the options hash to provide extra context to the generator
|
215
|
+
# @option opts [Fixnum] :min (0) minimum number of values in a generated Set
|
216
|
+
# @option opts [Fixnum] :max (*size*) maximum number of values in a generated Set
|
217
|
+
# @return [Radagen::Generator]
|
218
|
+
#
|
219
|
+
# @example
|
220
|
+
# set(fixnum).sample(5) #=> [#<Set: {}>, #<Set: {}>, #<Set: {1, -2}>, #<Set: {-3}>, #<Set: {-1, -4, -3}>]
|
221
|
+
#
|
222
|
+
def set(gen, opts={})
|
223
|
+
fmap(array(gen, opts)) do |array|
|
224
|
+
Set[*array]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Creates a generator that produces Hashes based on the *model_hash*
|
229
|
+
# passed in. This model_hash is a Hash of scalar keys and generator
|
230
|
+
# values. The hashes returned will have values realized from the
|
231
|
+
# generators provided.
|
232
|
+
#
|
233
|
+
# @param model_hash [Hash<Object, Radagen::Generator>] a hash of keys to generators
|
234
|
+
# @return [Radagen::Generator]
|
235
|
+
#
|
236
|
+
# @example
|
237
|
+
# hash({name: not_empty(string_alpha), age: fixnum_neg, occupation: elements([:engineer, :scientist, :chief])}).sample(4) #=> [{:name=>"r", :age=>-1, :occupation=>:engineer}, {:name=>"n", :age=>-1, :occupation=>:chief}, {:name=>"f", :age=>-2, :occupation=>:engineer}, {:name=>"O", :age=>-2, :occupation=>:chief}]
|
238
|
+
#
|
239
|
+
def hash(model_hash)
|
240
|
+
ks = model_hash.keys
|
241
|
+
vs = model_hash.values
|
242
|
+
raise ArgumentError.new 'all values in hash need to be a Gen::Generator' unless vs.all? { |g| gen? g }
|
243
|
+
|
244
|
+
fmap(tuple(*vs)) do |vs|
|
245
|
+
Hash[ks.zip(vs)]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Creates a generator that when called will return hashes containing
|
250
|
+
# keys taken from *key_gen* and values taken from *value_gen*.
|
251
|
+
#
|
252
|
+
# @note This will create hashes of various sizes and will grow along with the keys and values.
|
253
|
+
# @param key_gen [Radagen::Generator] creates the keys used in the hash
|
254
|
+
# @param value_gen [Radagen::Generator] creates the values used in the hash
|
255
|
+
# @return [Radagen::Generator]
|
256
|
+
#
|
257
|
+
# @example
|
258
|
+
# hash_map(symbol, fixnum_pos).sample(5) #=> [{}, {:t=>1}, {:DE=>2}, {:nvq=>1, :EN=>2, :L=>2}, {:QTCB=>4, :V=>3, :g=>3, :ue=>2}]
|
259
|
+
#
|
260
|
+
def hash_map(key_gen, value_gen)
|
261
|
+
fmap(array(tuple(key_gen, value_gen))) do |tuple_array|
|
262
|
+
Hash[tuple_array]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Creates a generator taking values from the passed in generator
|
267
|
+
# and applies them to the *pred* block, returning only the values
|
268
|
+
# that satisfy the predicate. This acts much the same way as
|
269
|
+
# enumerable's *#select* method. By default it will try 10 times
|
270
|
+
# to satisfy the predicate with different *sizes* passed to the
|
271
|
+
# generator. You can provide a count of the number of tries.
|
272
|
+
#
|
273
|
+
# @param gen [Radagen::Generator] generator that produces values applied to predicate block
|
274
|
+
# @param tries [Fixnum] (10) maximum number of tries the generator will make to satify the predicate
|
275
|
+
# @param pred [Proc] proc/function that takes a value from gen and returns a truthy or falsy value
|
276
|
+
# @return [Radagen::Generator]
|
277
|
+
#
|
278
|
+
# @example
|
279
|
+
# such_that(string_ascii) { |s| s.length > 4 }.sample(5) #=> ["/%daW", "bQ@t'", "{1%]o", "8j*vzL", "Ga2#Z"]
|
280
|
+
#
|
281
|
+
def such_that(gen, tries=10, &pred)
|
282
|
+
RG.new do |prng, size|
|
283
|
+
select_helper(gen, tries, prng, size, &pred)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Creates a generator that *empty* values from the provided
|
288
|
+
# generator are disregarded. Literally calls #empty? on object.
|
289
|
+
# This is a convenience generator for dealing with strings and
|
290
|
+
# collection types.
|
291
|
+
#
|
292
|
+
# @note Of course this will throw if you pass it a generator who's values don't produce types that respond to #empty?
|
293
|
+
# @param gen [Radagen::Generator] generator that produces values that #empty? will be called on
|
294
|
+
# @return [Radagen::Generator]
|
295
|
+
#
|
296
|
+
# @example
|
297
|
+
# not_empty(string_ascii).sample(5) #=> ["r", "!R", ";", "}", "HKh"]
|
298
|
+
#
|
299
|
+
# @example
|
300
|
+
# not_empty(array(string_ascii)).sample(5) #=> [["`"], ["", ""], ["", ""], ["MH(", "gM", "{mz"], ["!", "9;", "c"]]
|
301
|
+
#
|
302
|
+
def not_empty(gen)
|
303
|
+
such_that(gen) { |x| not x.empty? }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Creates a generator that when called will select one of the
|
307
|
+
# passed in generators returning it's realized value.
|
308
|
+
#
|
309
|
+
# @overload one_of(generator, generator, ...)
|
310
|
+
# @param gens [Radagen::Generator] generators
|
311
|
+
# @return [Radagen::Generator]
|
312
|
+
#
|
313
|
+
# @example
|
314
|
+
# one_of(fixnum, char_ascii, boolean).sample(5) #=> [0, "d", false, 1, -1]
|
315
|
+
#
|
316
|
+
def one_of(*gens)
|
317
|
+
bind(choose(0, gens.count - 1)) do |i|
|
318
|
+
gens.fetch(i)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Creates a generator that when called will select 1..n generators
|
323
|
+
# provided and return and Array of realized values from those chosen
|
324
|
+
# generators.
|
325
|
+
#
|
326
|
+
# @note Order of generator selection will be shuffled
|
327
|
+
# @overload some_of(generator, generator, ...)
|
328
|
+
# @param gens [Radagen::Generator] generators
|
329
|
+
# @return [Radagen::Generator]
|
330
|
+
#
|
331
|
+
# @example
|
332
|
+
# some_of(uuid, symbol, float).sample(4) #=> [["be136967-98e9-4b56-a562-1555c2d0dd2e"], [0.3905013788606524, "bf148e98-8454-4482-9b90-b0f3f2813785", :j], [0.40012165933893407, "8e6e4e58-75ed-4295-b53d-c60416c1a975", :bA], [-0.3612584756590138, :m]]
|
333
|
+
#
|
334
|
+
def some_of(*gens)
|
335
|
+
bind(tuple(*gens)) do |_vals|
|
336
|
+
bind(choose(1, _vals.count)) do |_count|
|
337
|
+
fmap(shuffle(_vals)) do |__vals|
|
338
|
+
__vals.take(_count)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns a generator that when called always returns
|
345
|
+
# the value passed in. The identity generator.
|
346
|
+
#
|
347
|
+
# @param value [Object] object that will always be returned by generator
|
348
|
+
# @return [Radagen::Generator]
|
349
|
+
#
|
350
|
+
# @example
|
351
|
+
# identity(["something"]).sample(4) #=> [["something"], ["something"], ["something"], ["something"]]
|
352
|
+
#
|
353
|
+
def identity(value)
|
354
|
+
RG.new { |_, _| value }
|
355
|
+
end
|
356
|
+
|
357
|
+
alias :return :identity
|
358
|
+
|
359
|
+
# Returns a generator that takes a single element from
|
360
|
+
# the passed in collection. Works with object that
|
361
|
+
# implement *.to_a*.
|
362
|
+
#
|
363
|
+
# @note Sampling is *with* replacement.
|
364
|
+
# @param coll [Object] collection object that an element will be selected from
|
365
|
+
# @return [Radagen::Generator]
|
366
|
+
#
|
367
|
+
# @example
|
368
|
+
# elements([1,2,3,4,5]).sample(4) #=> [4, 2, 5, 5]
|
369
|
+
#
|
370
|
+
# @example showing that #.to_a will be called on coll
|
371
|
+
# elements({:this => :this, :that => :that}).sample(2) #=> [[:that, :that], [:that, :that]]
|
372
|
+
#
|
373
|
+
def elements(coll)
|
374
|
+
_coll = coll.to_a
|
375
|
+
|
376
|
+
bind(choose(0, _coll.count - 1)) do |i|
|
377
|
+
identity(_coll.fetch(i))
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Returns a generator that will create a collection of
|
382
|
+
# same length with the elements reordered.
|
383
|
+
#
|
384
|
+
# @param coll [Object] collection object that elements will be reordered
|
385
|
+
# @return [Radagen::Generator]
|
386
|
+
#
|
387
|
+
# @example
|
388
|
+
# shuffle([:this, :that, :other]).sample(4) #=> [[:other, :that, :this], [:this, :that, :other], [:that, :other, :this], [:other, :this, :that]]
|
389
|
+
#
|
390
|
+
def shuffle(coll)
|
391
|
+
_coll = coll.to_a
|
392
|
+
_idx_gen = choose(0, _coll.length - 1)
|
393
|
+
|
394
|
+
fmap(array(tuple(_idx_gen, _idx_gen), {max: _coll.length * 3})) do |swap_indexes|
|
395
|
+
swap_indexes.reduce(_coll.clone) do |coll, (i, j)|
|
396
|
+
temp = coll[i]
|
397
|
+
coll[i] = coll[j]
|
398
|
+
coll[j] = temp
|
399
|
+
coll
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Returns a generator that will select a generator from the weighted
|
405
|
+
# hash basing the sampling probability on the weights. The weighted_hash
|
406
|
+
# is a hash where the keys are generators and values are the sampling
|
407
|
+
# weight relative to all other weights, allowing you to control the
|
408
|
+
# probability of value sampling.
|
409
|
+
#
|
410
|
+
# @param weighted_hash [Hash<Radagen::Generator, Fixnum>] a hash of generators to weights
|
411
|
+
# @return [Radagen::Generator]
|
412
|
+
#
|
413
|
+
# @example sample a uuid with three times the probability as string
|
414
|
+
# frequency({uuid => 3, string_ascii => 1}).sample(5) #=> ["", "Y", "3a1d740e-0587-40ca-a9e5-0e550e9afa0d", "2af98855-5bd8-43b4-8b80-1f0ef67712b3", "4fba95b5-9751-492f-889c-a850e7d9b313"]
|
415
|
+
#
|
416
|
+
def frequency(weighted_hash)
|
417
|
+
_weights = weighted_hash.values
|
418
|
+
_gens = weighted_hash.keys
|
419
|
+
raise ArgumentError.new 'all keys in kvs hash need to be Gen::Generator' unless _gens.all? { |g| gen? g }
|
420
|
+
|
421
|
+
bind(choose(0, _weights.reduce(&:+) - 1)) do |r|
|
422
|
+
frequency_helper(_gens, _weights, r, idx=0, sum=0)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Returns a generator that will create characters from codepoints 0-255.
|
427
|
+
#
|
428
|
+
# @note Defaults to UTF-8 encoding
|
429
|
+
# @return [Radagen::Generator]
|
430
|
+
#
|
431
|
+
# @example
|
432
|
+
# char.sample(5) #=> ["\u008F", "c", "0", "#", "x"]
|
433
|
+
#
|
434
|
+
def char
|
435
|
+
fmap(choose(0, 255)) do |v|
|
436
|
+
v.chr(CHAR_ENCODING)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# Returns a generator that will create ascii characters from codepoints 32-126.
|
441
|
+
#
|
442
|
+
# @note Defaults to UTF-8 encoding
|
443
|
+
# @return [Radagen::Generator]
|
444
|
+
#
|
445
|
+
# @example
|
446
|
+
# char_ascii.sample(5) #=> [".", "P", "=", ":", "l"]
|
447
|
+
#
|
448
|
+
def char_ascii
|
449
|
+
fmap(choose(32, 126)) do |v|
|
450
|
+
v.chr(CHAR_ENCODING)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# Returns a generator that will create alphanumeric characters from
|
455
|
+
# codepoint ranges: 48-57, 65-90, 97-122.
|
456
|
+
#
|
457
|
+
# @note Defaults to UTF-8 encoding
|
458
|
+
# @return [Radagen::Generator]
|
459
|
+
#
|
460
|
+
# @example
|
461
|
+
# char_alphanumeric.sample(5) #=> ["2", "A", "L", "I", "S"]
|
462
|
+
#
|
463
|
+
def char_alphanumeric
|
464
|
+
fmap(one_of(choose(48, 57), choose(65, 90), choose(97, 122))) do |v|
|
465
|
+
v.chr(CHAR_ENCODING)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Returns a generator that will create alpha characters from
|
470
|
+
# codepoint ranges: 65-90, 97-122.
|
471
|
+
#
|
472
|
+
# @note Defaults to UTF-8 encoding
|
473
|
+
# @return [Radagen::Generator]
|
474
|
+
#
|
475
|
+
# @example
|
476
|
+
# char_alpha.sample(5) #=> ["w", "p", "J", "W", "b"]
|
477
|
+
#
|
478
|
+
def char_alpha
|
479
|
+
fmap(one_of(choose(65, 90), choose(97, 122))) do |v|
|
480
|
+
v.chr(CHAR_ENCODING)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# Returns a generator that will create numeric characters from
|
485
|
+
# codepoints 48-57
|
486
|
+
#
|
487
|
+
# @note Defaults to UTF-8 encoding
|
488
|
+
# @return [Radagen::Generator]
|
489
|
+
#
|
490
|
+
# @example
|
491
|
+
# char_numeric.sample(5) #=> ["8", "5", "7", "0", "8"]
|
492
|
+
#
|
493
|
+
def char_numeric
|
494
|
+
fmap(choose(48, 57)) do |v|
|
495
|
+
v.chr(CHAR_ENCODING)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
# Returns a generator that will create strings from
|
500
|
+
# characters within codepoints 0-255
|
501
|
+
#
|
502
|
+
# @note Defaults to UTF-8 encoding
|
503
|
+
# @return [Radagen::Generator]
|
504
|
+
#
|
505
|
+
# @example
|
506
|
+
# string.sample #=> ["", "®", "M", "", "", "Ù¡", "¾H", "<*<", "=\u000FW\u0081", "m¦w"]
|
507
|
+
#
|
508
|
+
def string
|
509
|
+
fmap(array(char)) do |char_array|
|
510
|
+
char_array.join
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
# Returns a generator that will create strings from
|
515
|
+
# ascii characters within codepoints 32-126
|
516
|
+
#
|
517
|
+
# @note Defaults to UTF-8 encoding
|
518
|
+
# @return [Radagen::Generator]
|
519
|
+
#
|
520
|
+
# @example
|
521
|
+
# string_ascii.sample #=> ["", "^", "", "", "M5a", "", "c", "/gL`Q\\W", "D$I0:F", "`hC|w"]
|
522
|
+
#
|
523
|
+
def string_ascii
|
524
|
+
fmap(array(char_ascii)) do |char_array|
|
525
|
+
char_array.join
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Returns a generator that will create strings from
|
530
|
+
# alphanumeric characters within codepoint ranges:
|
531
|
+
# 48-57, 65-90, 97-122
|
532
|
+
#
|
533
|
+
# @note Defaults to UTF-8 encoding
|
534
|
+
# @return [Radagen::Generator]
|
535
|
+
#
|
536
|
+
# @example
|
537
|
+
# string_alphanumeric.sample #=> ["", "e", "yh", "8", "", "8z0", "u441", "L3", "257o", "gWGhZ9"]
|
538
|
+
#
|
539
|
+
def string_alphanumeric
|
540
|
+
fmap(array(char_alphanumeric)) do |char_array|
|
541
|
+
char_array.join
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
# Returns a generator that will create strings from
|
546
|
+
# alpha characters within codepoint ranges: 65-90, 97-122
|
547
|
+
#
|
548
|
+
# @note Defaults to UTF-8 encoding
|
549
|
+
# @return [Radagen::Generator]
|
550
|
+
#
|
551
|
+
# @example
|
552
|
+
# string_alpha.sample #=> ["", "", "", "H", "i", "Nxc", "gPfIt", "KpGRCl", "BjuiQE", "FCnfPkr"]
|
553
|
+
#
|
554
|
+
def string_alpha
|
555
|
+
fmap(array(char_alpha)) do |char_array|
|
556
|
+
char_array.join
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
# Returns a generator that will create strings from
|
561
|
+
# alpha characters within codepoints 48-57
|
562
|
+
#
|
563
|
+
# @note Defaults to UTF-8 encoding
|
564
|
+
# @return [Radagen::Generator]
|
565
|
+
#
|
566
|
+
# @example
|
567
|
+
# string_alpha.sample #=> ["", "", "", "H", "i", "Nxc", "gPfIt", "KpGRCl", "BjuiQE", "FCnfPkr"]
|
568
|
+
#
|
569
|
+
def string_numeric
|
570
|
+
fmap(array(char_numeric)) do |char_array|
|
571
|
+
char_array.join
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
# Returns a generator that will return either true or false.
|
576
|
+
#
|
577
|
+
# @return [Radagen::Generator]
|
578
|
+
#
|
579
|
+
# @example
|
580
|
+
# boolean.sample(5) #=> [false, true, false, false, true]
|
581
|
+
#
|
582
|
+
def boolean
|
583
|
+
elements([false, true])
|
584
|
+
end
|
585
|
+
|
586
|
+
# Returns a generator that will return fixnums.
|
587
|
+
#
|
588
|
+
# @return [Radagen::Generator]
|
589
|
+
#
|
590
|
+
# @example
|
591
|
+
# fixnum.sample(5) #=> [0, 0, -1, 0, -2]
|
592
|
+
#
|
593
|
+
def fixnum
|
594
|
+
sized { |size| choose(-size, size) }
|
595
|
+
end
|
596
|
+
|
597
|
+
# Returns a generator that will return positive fixnums.
|
598
|
+
#
|
599
|
+
# @note 0 is excluded
|
600
|
+
# @return [Radagen::Generator]
|
601
|
+
#
|
602
|
+
# @example
|
603
|
+
# fixnum_pos.sample(5) #=> [1, 1, 3, 2, 4]
|
604
|
+
#
|
605
|
+
def fixnum_pos
|
606
|
+
such_that(natural) { |f| f > 0 }
|
607
|
+
end
|
608
|
+
|
609
|
+
# Returns a generator that will return negative fixnums.
|
610
|
+
#
|
611
|
+
# @note 0 is excluded
|
612
|
+
# @return [Radagen::Generator]
|
613
|
+
#
|
614
|
+
# @example
|
615
|
+
# fixnum_neg.sample(5) #=> [-1, -1, -1, -2, -2]
|
616
|
+
#
|
617
|
+
def fixnum_neg
|
618
|
+
fmap(fixnum_pos) { |f| f * -1 }
|
619
|
+
end
|
620
|
+
|
621
|
+
# Returns a generator that will return natural fixnums.
|
622
|
+
#
|
623
|
+
# @note 0 is included
|
624
|
+
# @return [Radagen::Generator]
|
625
|
+
#
|
626
|
+
# @example
|
627
|
+
# natural.sample(5) #=> [0, 1, 2, 3, 0]
|
628
|
+
#
|
629
|
+
def natural
|
630
|
+
fmap(fixnum) { |f| f.abs }
|
631
|
+
end
|
632
|
+
|
633
|
+
# Returns a generator that will return Floats.
|
634
|
+
#
|
635
|
+
# @return [Radagen::Generator]
|
636
|
+
#
|
637
|
+
# @example
|
638
|
+
# float.sample(5) #=> [0.0, -0.2676817207773654, 1.0318897544602246, 0.025701283892250792, -3.694547741510407]
|
639
|
+
#
|
640
|
+
def float
|
641
|
+
sized { |size| choose(-size.to_f, size.to_f) }
|
642
|
+
end
|
643
|
+
|
644
|
+
# Returns a generator that will return Rationals.
|
645
|
+
#
|
646
|
+
# @return [Radagen::Generator]
|
647
|
+
#
|
648
|
+
# @example
|
649
|
+
# rational.sample(5) #=> [(0/1), (-1/1), (1/1), (-1/2), (0/1)]
|
650
|
+
#
|
651
|
+
def rational
|
652
|
+
denom_gen = such_that(fixnum) { |f| not f == 0 }
|
653
|
+
|
654
|
+
fmap(tuple(fixnum, denom_gen)) do |(n, d)|
|
655
|
+
Rational(n, d)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
# Returns a generator that will return Byte array representations.
|
660
|
+
#
|
661
|
+
# @return [Radagen::Generator]
|
662
|
+
#
|
663
|
+
# @example
|
664
|
+
# bytes.sample(5) #=> [[194, 187], [58], [194, 139], [75], [195, 186, 195, 181, 56]]
|
665
|
+
#
|
666
|
+
def bytes
|
667
|
+
fmap(not_empty(string)) { |s| s.bytes }
|
668
|
+
end
|
669
|
+
|
670
|
+
# Returns a generator that will return bytes represented as a
|
671
|
+
# string of hex characters. Similar to Random.new.bytes
|
672
|
+
#
|
673
|
+
# @return [Radagen::Generator]
|
674
|
+
#
|
675
|
+
# @example
|
676
|
+
# byte_string.sample(5) #=> [ "8&\xB6\xD3\x10", "#\xE3RS\x92 ", "\xC2c\xD2\x19,*", "2", "\xD2s\x80" ]
|
677
|
+
#
|
678
|
+
def byte_string
|
679
|
+
fmap(not_empty(string)) { |s| [s].pack('H*') }
|
680
|
+
end
|
681
|
+
|
682
|
+
# Returns a generator that will return Ruby Symbols.
|
683
|
+
#
|
684
|
+
# @return [Radagen::Generator]
|
685
|
+
#
|
686
|
+
# @example
|
687
|
+
# symbol.sample(5) #=> [:"7K", :a, :"82", :lhK, :qI4]
|
688
|
+
#
|
689
|
+
def symbol
|
690
|
+
fmap(not_empty(array(char_alphanumeric))) do |char_array|
|
691
|
+
char_array.join.intern
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
# Returns a random type 4 uuid.
|
696
|
+
#
|
697
|
+
# @note *size* does not effect derived UUIDs
|
698
|
+
# @return [Radagen::Generator]
|
699
|
+
#
|
700
|
+
# @example
|
701
|
+
# uuid.sample(2) #=> ["1e9a99d0-d412-4362-a36a-6754c055a016", "d28326d7-db57-43ce-85f6-929d73aa3cbf"]
|
702
|
+
#
|
703
|
+
def uuid
|
704
|
+
fmap(array(choose(0, 15), {min: 31, max: 31})) do |nibbles|
|
705
|
+
rhex = (8 + (nibbles[15] & 3)).to_s(16)
|
706
|
+
|
707
|
+
[nibbles[0].to_s(16), nibbles[1].to_s(16), nibbles[2].to_s(16), nibbles[3].to_s(16),
|
708
|
+
nibbles[4].to_s(16), nibbles[5].to_s(16), nibbles[6].to_s(16), nibbles[7].to_s(16), '-',
|
709
|
+
nibbles[8].to_s(16), nibbles[9].to_s(16), nibbles[10].to_s(16), nibbles[11].to_s(16), '-',
|
710
|
+
4, nibbles[12].to_s(16), nibbles[13].to_s(16), nibbles[14].to_s(16), '-',
|
711
|
+
rhex, nibbles[16].to_s(16), nibbles[17].to_s(16), nibbles[18].to_s(16), '-',
|
712
|
+
nibbles[19].to_s(16), nibbles[20].to_s(16), nibbles[21].to_s(16), nibbles[22].to_s(16),
|
713
|
+
nibbles[23].to_s(16), nibbles[24].to_s(16), nibbles[25].to_s(16), nibbles[26].to_s(16),
|
714
|
+
nibbles[27].to_s(16), nibbles[28].to_s(16), nibbles[29].to_s(16), nibbles[30].to_s(16)].join
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
# Returns a random selection of a *simple* type.
|
719
|
+
#
|
720
|
+
# @note *size* does not effect derived UUID
|
721
|
+
# @return [Radagen::Generator]
|
722
|
+
#
|
723
|
+
# @example
|
724
|
+
# simple_type.sample(5) #=> ["Á", -0.6330060889340847, (-1/1), "Vé", 1]
|
725
|
+
#
|
726
|
+
def simple_type
|
727
|
+
one_of(fixnum, rational, bytes, float, boolean, symbol, char, string, uuid)
|
728
|
+
end
|
729
|
+
|
730
|
+
# Returns a random selection of a screen printable *simple* type.
|
731
|
+
#
|
732
|
+
# @note *size* does not effect derived UUID
|
733
|
+
# @return [Radagen::Generator]
|
734
|
+
#
|
735
|
+
# @example
|
736
|
+
# simple_printable.sample(5) #=> [0, "w", "tr", "aO", -3.072343865486716]
|
737
|
+
#
|
738
|
+
def simple_printable
|
739
|
+
one_of(fixnum, rational, float, boolean, symbol, char_ascii, string_ascii, char_alphanumeric, string_alphanumeric, uuid)
|
740
|
+
end
|
741
|
+
|
742
|
+
private
|
743
|
+
|
744
|
+
RG = Radagen::Generator
|
745
|
+
|
746
|
+
CHAR_ENCODING = Encoding::UTF_8
|
747
|
+
|
748
|
+
def frequency_helper(gens, weights, r, idx, sum)
|
749
|
+
if r < weights[idx] + sum
|
750
|
+
gens[idx]
|
751
|
+
else
|
752
|
+
frequency_helper(gens, weights, r, idx + 1, weights[idx] + sum)
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
def select_helper(gen, tries, prng, size, &pred)
|
757
|
+
raise RangeError.new "Exceeded number of tries to satisfy predicate." unless tries >= 1
|
758
|
+
unless tries <= 0
|
759
|
+
_val = gen.call(prng, size)
|
760
|
+
if pred.call(_val)
|
761
|
+
_val
|
762
|
+
else
|
763
|
+
select_helper(gen, tries - 1, prng, size + 1, &pred)
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
end
|