For my current project, I’ve decided to improve one of my random int generators, which is supposed to favour small or large numbers within a given array. I googled and found out that a proper solution would involve a cumulative distribution function for probabilities, so I tried to build one myself. I’m no math wiz, but the results of my tests have been promising (I’ve included my test below). I’m looking to build an efficient and effective solution, so any suggestions would be appreciated! Also, please feel free to use this code if it’s helpful!
Where pInts
is an array of integers, and pWeight
is a float between [-1,1] (inclusive) telling the function how heavily it should favour large values (positive) or small values (negative):
define :chooseFavouredAbsInt do |pInts, pWeight|
sortedUniqAbsValues = pInts.map { |v| v.abs }.uniq.sort
if (pWeight.zero? || (sortedUniqAbsValues.length < 2))
return pInts.choose
else
minInt = sortedUniqAbsValues.first
maxInt = sortedUniqAbsValues.last
refInts = (minInt..maxInt).to_a
diffs = refInts.map { |v| (v - minInt) }
partialSum = 0
partialSums = diffs.take(diffs.length)
diffs.each_index do |i|
partialSum += diffs[i]
partialSums[i] = partialSum
end
probabilities = partialSums.map { |ps| ((ps * pWeight.abs) / partialSums.last.to_f) }
if (pWeight > 0)
pool = pInts.select { |i| evalChance?(probabilities[refInts.index(i.abs)]) }
while pool.empty?
pool = pInts.select { |i| evalChance?(probabilities[refInts.index(i.abs)]) }
end
return pool.choose
else
pool = pInts.select { |i| evalChance?(1 - probabilities[refInts.index(i.abs)]) }
while pool.empty?
pool = pInts.select { |i| evalChance?(1 - probabilities[refInts.index(i.abs)]) }
end
return pool.choose
end
end
end
The above code depends on this little function, which returns a boolean by evaluating a given chance (a float between [0,1] inclusive):
define :evalChance? do |pChance|
return ((pChance >= 1) || ((pChance > 0) && (rand() < pChance)))
end
I’ve tested my code with the following:
randomSeed = Time.new.to_i
testInts = (0...10).to_a
trials = 100000
successes = 0
use_random_seed(randomSeed)
trials.times do
successes += 1 if (testInts.choose >= 5)
end
puts("random choose >= 5: #{successes.to_s}")
successes = 0
use_random_seed(randomSeed)
trials.times do
successes += 1 if (chooseFavouredAbsInt(testInts, 0.8) >= 5)
end
puts("favoured choose >= 5: #{successes.to_s}")
successes = 0
use_random_seed(randomSeed)
trials.times do
successes += 1 if (testInts.choose < 5)
end
puts("random choose < 5: #{successes.to_s}")
successes = 0
use_random_seed(randomSeed)
trials.times do
successes += 1 if (chooseFavouredAbsInt(testInts, -0.8) < 5)
end
puts("favoured choose < 5: #{successes.to_s}")
The results are what I would expect. Something like this:
├─ “random choose >= 5: 49674”
├─ “favoured choose >= 5: 88853”
├─ “random choose < 5: 50326”
└─ “favoured choose < 5: 65728”