Class: Forall::Random

Inherits:
Object
  • Object
show all
Defined in:
lib/forall/random.rb

Instance Method Summary collapse

Constructor Details

#initialize(seed: nil) ⇒ Random

Returns a new instance of Random.



5
6
7
# File 'lib/forall/random.rb', line 5

def initialize(seed: nil)
  @prng = ::Random.new(seed || ::Random.new_seed)
end

Instance Method Details

#array(size: nil) ⇒ Object

Generates an Array by repeatedly calling a block that returns a random value.

Examples:

rnd.array         {|n| n }          #=> [0,1,2,3,...]
rnd.array(10..50) { integer(0..9) } #=> [8,2,1,1,...]

Returns:

  • Array



247
248
249
250
251
# File 'lib/forall/random.rb', line 247

def array(size: nil)
  size ||= integer(0..64)
  size = choose(size) if Range === size
  size.times.map{|n| yield n }
end

#booleanTrueClass | FalseClass

Returns:

  • (TrueClass | FalseClass)


15
16
17
# File 'lib/forall/random.rb', line 15

def boolean
  @prng.rand >= 0.5
end

#date(range = nil) ⇒ Date

Returns a randomly chosen Date within the given bounds

Parameters:

  • range (Range<Date>) (defaults to: nil)

Returns:

  • (Date)


43
44
45
46
47
# File 'lib/forall/random.rb', line 43

def date(range = nil)
  min = (range&.min || Date.civil(0000, 1, 1)).to_time
  max = (range&.max || Date.civil(9999,12,31)).to_time + 86399
  Time.at(float(min.to_f .. max.to_f)).to_date
end

#datetime(range = nil) ⇒ DateTime

Returns a randomly chosen DateTime within the given bounds

Parameters:

  • range (Range<DateTime>) (defaults to: nil)

Returns:

  • (DateTime)


69
70
71
72
73
# File 'lib/forall/random.rb', line 69

def datetime(range = nil)
  min = range&.min&.to_time
  max = range&.max&.to_time
  time(min..max).to_datetime
end

#float(range = nil) ⇒ Float

Returns a randomly chosen floating point number

Returns:

  • (Float)


33
34
35
36
37
# File 'lib/forall/random.rb', line 33

def float(range = nil)
  min = range&.min || Float::MIN
  max = range&.max || Float::MAX
  @prng.rand(min..max)
end

#hash(size: nil) {|| ... } ⇒ Hash<K, V>

Generates a Hash by repeatedly calling a block that returns a random [key, val] pair.

Yield Parameters:

  • (Integer)

Yield Returns:

  • (Array<K, V>)

Returns:

  • (Hash<K, V>)


259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/forall/random.rb', line 259

def hash(size: nil)
  size ||= integer(0..64)
  size = choose(size) if Range === size
  hash = {}

  until hash.size >= size
    k, v = yield(hash.size)
    hash[k] = v
  end

  hash
end

#integer(range = nil) ⇒ Integer

Returns a randomly chosen integer within the given bounds

Parameters:

  • range (Range<Integer>) (defaults to: nil)

Returns:

  • (Integer)


23
24
25
26
27
# File 'lib/forall/random.rb', line 23

def integer(range = nil)
  min = range&.min || -2**64-1
  max = range&.max ||  2**64+1
  @prng.rand(min..max)
end

#permutation(size: nil) ⇒ Array<Integer>

Generates a random permutation of the given size

Parameters:

  • size (Intege) (defaults to: nil)

Returns:

  • (Array<Integer>)


235
236
237
# File 'lib/forall/random.rb', line 235

def permutation(size: nil)
  (0..(size || integer(0..64))-1).to_a.shuffle!(random: @prng)
end

#range(range, width: nil) ⇒ Range

Returns a randomly chosen range within the given bounds

Parameters:

  • range (Range<Object>)
  • width (Integer) (defaults to: nil)

Returns:

  • (Range)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/forall/random.rb', line 80

def range(range, width: nil)
  min = range.min
  max = range.max

  if width.nil?
    case min or max
    when Float
      a = float(range)
      b = float(range)
    when Integer
      a = integer(range)
      b = integer(range)
    when Time
      a = time(range)
      b = time(range)
    when Date
      a = date(range)
      b = date(range)
    when DateTime
      a = datetime(range)
      b = datetime(range)
    else
      a, b = choose(range, count: 2)
    end

    if a < b
      min = a
      max = b
    else
      min = b
      max = a
    end
  else
    # Randomly choose a width within given bounds
    width = choose(width) if Enumerable === width

    case min or max
    when Float
      min = float(min: min, max: max-width)
      max = min+width-1
    when Integer
      min = integer(min: min, max: max-width)
      max = min+width-1
    else
      all = (min..max).to_a
      max = all.size-1

      # Randomly choose element indices
      min = integer(min: 0, max: max-width)
      max = min+width-1

      min = all[min]
      max = all[max]
    end
  end

  min..max
end

#sample(items, count: nil) ⇒ Object Also known as: choose

Returns a uniformly random chosen element(s) from the given Enumerable

Parameters:

Returns:

  • (Object)


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/forall/random.rb', line 143

def sample(items, count: nil)
  case items
  when Input
    items.sample(self, count: count)
  when Range
    method =
      if                           Integer  === (items.min || items.max) then :integer
      elsif                        Float    === (items.min || items.max) then :float
      elsif                        Time     === (items.min || items.max) then :time
      elsif defined?(Date)     and Date     === (items.min || items.max) then :date
      elsif defined?(DateTime) and DateTime === (items.min || items.max) then :datetime
      else
        # NOTE: This is memory inefficient
        items = items.to_a

        if count.nil?
          return items.sample(random: @prng)
        else
          return count.times.map{|_| items.sample(random: @prng) }
        end
      end

    if count.nil?
      send(method, items)
    else
      count.times.map{|_| send(method, items) }
    end
  else
    unless items.respond_to?(:sample)
      # NOTE: This works across many types but is memory inefficient
      items = items.to_a
    end

    if count.nil?
      items.sample(random: @prng)
    else
      # Sample *with* replacement
      count.times.map{|_| items.sample(random: @prng) }
    end
  end
end

#seedInteger

Returns:

  • (Integer)


10
11
12
# File 'lib/forall/random.rb', line 10

def seed
  @prng.seed
end

#set(size: nil) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
# File 'lib/forall/random.rb', line 272

def set(size: nil)
  size ||= integer(0..64)
  size = choose(size) if Range === size
  set  = Set.new

  until set.size == size
    set << yield(set.size)
  end

  set
end

#shuffle(items) ⇒ Array<A>

Parameters:

  • items (Array<A>)

Returns:

  • (Array<A>)


227
228
229
# File 'lib/forall/random.rb', line 227

def shuffle(items)
  items.shuffle(random: @prng)
end

#time(range = nil, utc: nil) ⇒ Time

Returns a randomly chosen Time within the given bounds

Parameters:

  • range (Range<Time>) (defaults to: nil)

Returns:

  • (Time)


53
54
55
56
57
58
59
60
61
62
63
# File 'lib/forall/random.rb', line 53

def time(range = nil, utc: nil)
  min = range&.min || Time.utc(0000,1,1,0,0,0)
  max = range&.max || Time.utc(9999,12,31,23,59,59)
  rnd = float(min.to_f .. max.to_f)

  if utc or (utc.nil? and (min.utc? or max.utc?))
    Time.at(rnd).utc
  else
    Time.at(rnd)
  end
end

#weighted(items, freqs, count: nil) ⇒ Object

Returns a uniformly random chosen element(s) from the given Enumerable

Parameters:

  • items (Array<Object>)
  • freqs (Array<Numeric>)
  • count (Numeric) (defaults to: nil)

Returns:

  • (Object)


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/forall/random.rb', line 193

def weighted(items, freqs, count: nil)
  unless items.size == freqs.size
    raise ArgumentError, "items and frequencies must have same size"
  end

  # This runs in O(n) time where n is the number of possible items. This is
  # not dependent on `count`, the number of requested items.
  if count.nil?
    sum = freqs[0].to_f
    res = items[0]

    (1..items.size - 1).each do |i|
      sum += freqs[i]
      p = freqs[i] / sum
      j = @prng.rand
      res = items[i] if j <= p
    end
  else
    sum = freqs[0, count].sum.to_f
    res = items[0, count]

    (count..items.size).each do |i|
      sum += freqs[i]
      p = count * freqs[i] / sum
      j = @prng.rand
      res[@prng.rand(count)] = items[i] if j <= p
    end
  end

  res
end