1

Random range in ruby presented as

rand(2..11)

Lets say I would love number 2 or any other number in the range to be repeated not more than N amount of times.

Is the way to do it?

Misha
  • 1,876
  • 2
  • 17
  • 24

2 Answers2

2

I think there is no default way to do that. You need to write some code, for example check this answer from Marc Talbot: https://stackoverflow.com/a/9458016/1306709


a = (100..999).to_a.shuffle 

then every time you need a new id

new_id = a.pop

This guarantees that numbers are never reused. Of course, you'll have problems when you run out of elements on the array.


so if you need a few times of repeating - combine a few sets with similar numbers and pop from it.

Community
  • 1
  • 1
BoP
  • 417
  • 4
  • 12
  • 4
    To get repeated items, "multiply" the array: `a = ((100..999).to_a * n).shuffle`. – Jordan Running Feb 18 '16 at 23:26
  • 1
    Боря дякую :). This is not good example for me since he aims to non-repeating random numbers. I need limited amount of repeats to the random number. – Misha Feb 18 '16 at 23:28
2

You can do that by performing the following steps:

  • Obtain an unconstrained sample
  • If there are n instances of the limited repeat value, and at most m are permitted, delete all n and add back m
  • Add a random selection of n-m values from an array comprised of elements of the initial range less the limited repeat value
  • Shuffle the resulting array

Code

def random_with_limit(range, sample_size, limited_repeat_value, max_instances)
  a = Array.new(sample_size) { rand range }
  extra = a.count(limited_repeat_value) - max_instances
  puts "inital number of #{limited_repeat_value} = #{max_instances+extra}"
  return a if extra <= 0
  b = [*range]-[limited_repeat_value]
  (a-[limited_repeat_value]).
    concat([limited_repeat_value]*max_instances).
    concat(Array.new(extra) { b.sample }).
    shuffle
end

The puts statement is included merely for illustration.

Examples

I've created a helper for displaying numbers of each element of the range that are randomly selected. This method also prints the number of instances of the specified element in the initial unrestricted sample.

def show_result(range, sample_size, limited_repeat_value, max_instances)
  random_with_limit(2..11, 50, 4, 3).sort.chunk(&:itself).map { |n,dups|
    "#{n} (#{dups.count})" }
end

show_result(2..11, 50, 4, 3)
  # inital number of 4 = 4
  #=> ["2 (6)", "3 (10)", "4 (3)", "5 (5)", "6 (5)",
  #    "7 (6)", "8 (5)", "9 (2)", "10 (6)", "11 (2)"] 
show_result(2..11, 50, 4, 3)
  # inital number of 4 = 3
  #=> ["2 (7)", "3 (5)", "4 (3)", "5 (3)", "6 (4)",
  #    "7 (6)", "8 (5)", "9 (6)", "10 (7)", "11 (4)"] 
show_result(2..11, 50, 4, 3)
  # inital number of 4 = 2
  #=> ["2 (5)", "3 (5)", "4 (2)", "5 (4)", "6 (8)",
  #    "7 (8)", "8 (1)", "9 (7)", "10 (4)", "11 (6)"]
show_result(2..11, 50, 4, 3)
  # inital number of 4 = 7
  #=> ["2 (2)", "3 (8)", "4 (3)", "5 (8)", "6 (5)",
  #    "7 (3)", "8 (3)", "9 (6)", "10 (5)", "11 (7)"] 

Alternative approach

  • Determine indices of instances of the limited repeat value
  • If there n > m, select n-m at random
  • Replace the values at the n-m selected indices with random values selected as in the first solution

def random_with_limit(range, sample_size, limited_repeat_value, max_instances)
  a = Array.new(sample_size) { rand range }
  extra = a.count(limited_repeat_value) - max_instances
  puts "inital number of #{limited_repeat_value} = #{max_instances+extra}"
  return a if extra <= 0
  idx = a.each_with_index.select { |x,_| x == limited_repeat_value }.map(&:last)
  b = [*range]-[limited_repeat_value]
  idx.shuffle.first(extra).each { |i| a[i] = b.sample }
  a
end

show_result(2..11, 50, 4, 3)
  # inital number of 4 = 6
  # => ["2  (2)", "3 (3)", "4 (3)",  "5 (4)",  "6 (7)",
  #     "7 (12)", "8 (4)", "9 (7)", "10 (5)", "11 (3)"] 
show_result(2..11, 50, 4, 3)
  # inital number of 4 = 2
  #=> ["2 (9)", "3 (3)", "4 (2)",  "5 (6)",  "6 (6)",
  #    "7 (3)", "8 (2)", "9 (5)", "10 (9)", "11 (5)"] 
show_result(2..11, 50, 4, 3)
  # inital number of 4 = 4
  #=> ["2 (3)", "3 (3)", "4 (3)",  "5 (5)",  "6 (4)",
  #    "7 (7)", "8 (6)", "9 (4)", "10 (7)", "11 (8)"] 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100