radagen 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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