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