Let's say I have lots of cash I want to give out in random increments between $500 and $5,000. I'd like the lower end of the range to be much more probable than the higher range. What's the most efficient algorithm I can write in Ruby to randomly hand out cash in this manner?
问题:
回答1:
I think the easiest way is to just use the case statement:
def get_price
case rand(100) + 1
when 1..50 then 500
when 50..75 then 1000
when 75..99 then 2500
when 99..100 then 10000
end
end
p get_price # => 500
Calling get_price will return 500 with 50% probability but 10000 will only be returned 2% of the time.
回答2:
One approach is to start by defining a number of ranges and assigning a probability 'weight' for each. Here's an example:
weights = {[500, 1000] => 17,
[1001, 1500] => 15,
[1501, 2000] => 13,
[2001, 2500] => 12,
[2501, 3000] => 11,
[3001, 3500] => 10,
[3501, 4000] => 10,
[4001, 4500] => 10,
[4501, 5000] => 10}
Here, the range [1001, 1500]
, with weight 15, is 50% more likely to be selected than any of the four highest ranges, which each have a weight of 10. You can have any number of ranges and (as here) weights need not sum to 100. Here you could replace the four highest ranges with the single range [3001, 5000] => 40
.
The idea is to select a range at random, using the weights you've provided and then select a random value within that range, where each value within the range is equally likely to be chosen.
ranges = weights.keys # => [[500, 1000], [1001, 1500],.., [4501, 5000]]
cum_wights = weights.values # => [17, 15,.., 10]
(1..weights.size-1).each {|i| cum_weights[i] += cum_weights[i-1]}
# cum_weights => [17, 32,.., 108]
# Obtain range randomly
rn = rand(cum_weights) # => random number between 0 and cum_weights.last (here 108)
i = 0 # range index
i += 1 while cum_weights[i] <= rn
rr = ranges[i] # random range
# Obtain uniform random value in range rr
# Obtain uniform random value in range i
# Since `rn` is equally-likely for any value in `rr`,
cwt_min, cwt_max = (i > 0 ? cum_weights[i-1] + 1 : 0), cum_weights[i]
random_amount = rr.first + ((rn - cwt_min).to_f/(cwt_max - cwt_min + 1)) * (rr.last-rr.first + 1)
or simply generate another random number:
random_amount = rr.first + rand(rr.last-rr.first)
Incidentally, I am very experienced at giving away random amounts of money. Let me know if I can help.
回答3:
Sounds like you want to generate random numbers with a Gaussian (aka normal) distribution skewed towards the left (or right, I can never keep them straight; the hump goes on the left).
A VBA implementation of the algorithm for sampling a skewed normal distribution is here (warning: popups): http://www.ozgrid.com/forum/showthread.php?t=108175
It ought not be difficult to translate it to ruby.