Help me with advanced math formula

I’m trying to make a function to give a random size to something, but I want low numbers to be more frequent (the lower the more frequent) and the opposite for larger numbers. I have this formula but it doesn’t behave as I want. What I need is something like Grow A Garden (it’s unlikely to grow a giant tree) but it still fluctuates on smaller numbers.

Sorry if this seems a bit confusing

function Exponent_Biasing(Rng, baseSize)
	local RngRandom = Random.new(Rng)
	local r = RngRandom:NextNumber()

	local minSize = baseSize
	local maxSize = baseSize * 150

	local curved = r^6 -- The bigger the more difficult it is to reach Maxsize

	local size = curved * (maxSize - minSize) + minSize
	
	return size
end

local TestArray = {}

for i = 1,100 do
	local size = Exponent_Biasing(math.random(1,100000),1)
	TestArray[math.floor(size)] = (TestArray[math.floor(size)] or 0) + 1
end

print(TestArray)

If anyone can help me, I would be very grateful!

I guess you want your size variable to be continuous, not discrete. In this case I suggest you look into statistical distributions.

An exponential distribution would be quite fitting. If you are unfimiliar with distributions, its very unintuitive to use lambda to model the distribution.

Im in a rush, so this must suffice for now :+1: If you need more help - maybe a full explanation on how to apply this, let me know. I’ll be back in couple of hours :smiley:

I would appreciate it if you could explain it to me in more depth, but do it at your own pace, thanks in advance!

Can you please clarify a bit on the issue?
I tested out your code (using n=1000000 rather than 100) and it seems to behave as i would imagine you would have wanted?

Is the issue that the curve is too steep at the start?


I’m not satisfied with the result, initially it seems to work well, but it’s not well distributed at first

exactly,I think that’s the problem

Would this distribution be more ideal, or is it still too clumped up at the beginning?

What formula did you use? I have to test it to see how it behaves.

The formula is the same, I just ommitted the first value and shifted the rest left by 1.
There’s probably a way to guarantee that the value isn’t 1, but for now the simplest way I can think of is a basic recursion:

function Exponent_Biasing(Rng, baseSize)
	local RngRandom = Random.new(Rng)
	local r = RngRandom:NextNumber()
	local minSize = baseSize
	local maxSize = baseSize * (150 + 1)
	local curved = r^6 -- The bigger the more difficult it is to reach Maxsize
	local size = curved * (maxSize - minSize) + minSize
	return (size > 2) and (size - 1) or Exponent_Biasing(Rng+1, baseSize)
end

It should be somewhat closer to what you want. If not, I can try to come up with a new formula entirely (in which case, could you please try to draw the distribution how you want it in something like microsoft paint, so we have an idea of what shape you are after?)

I think you need a condition. For example, if a player has planted 9 plants, then 10 will be a large plant.

I got something like this:

function Discrete_Biased_Simple(Rng, minVal)
	local RngRandom = Random.new(Rng)
	local r = RngRandom:NextNumber()

	local maxVal = minVal * 150
	local bias = 50
	
	local curved = r ^ bias

	local val = curved * (maxVal - minVal + 1) + minVal
	return val
end

(It still doesn’t seem to be what I’m looking for)

Can you please provide a sample of the distribution that you want? Without it, we will just be guessing aimlessly, and that would be a waste of everyone’s time

@native function ExponentialBiased(minNumber: number, maxNumber: number, lambda: number): number
	assert(lambda > 0, "Lambda must be a positive number.")

	local u = Random.new():NextNumber()

	-- This is the correct formula for inverse transform sampling on a
	-- truncated exponential distribution.
	-- It avoids the "clamping" problem and preserves the distribution's shape.
	local term1 = math.exp(-lambda * minNumber)
	local term2 = math.exp(-lambda * maxNumber)

	local inner_log = term1 - u * (term1 - term2)

	local finalValue = (-1 / lambda) * math.log(inner_log)

	return finalValue
end

function calculateMinMaxMean(data: { number }): (number, number, number)
	if #data == 0 then return nil, nil, nil end
	local min = data[1]
	local max = data[1]
	local sum = 0
	for _, value in ipairs(data) do
		if value < min then min = value end
		if value > max then max = value end
		sum = sum + value
	end
	local mean = sum / #data
	return min, max, mean
end

function calculateMedian(data: { number }): number
	if #data == 0 then return nil end
	local sortedData = {}
	for _, v in ipairs(data) do
		table.insert(sortedData, v)
	end
	table.sort(sortedData)

	local n = #sortedData
	if n % 2 == 1 then
		return sortedData[math.floor(n / 2) + 1]
	end
	local mid1 = sortedData[n / 2]
	local mid2 = sortedData[n / 2 + 1]
	return (mid1 + mid2) / 2
end

function calculateMode(data: { number }): number
	if #data == 0 then return nil end
	local counts = {}
	for _, value in ipairs(data) do
		counts[value] = (counts[value] or 0) + 1
	end

	local mode = nil
	local maxCount = 0
	for value, count in pairs(counts) do
		if count > maxCount then
			maxCount = count
			mode = value
		end
	end
	return mode
end

local chances = {}
local TestArray = {}
local TryAmount = 1e3

for i = 1,TryAmount do
	local size = ExponentialBiased(1, 1e5, 0.05) -- higher lambda = more rarer, like 0.1
	TestArray[math.floor(size)] = (TestArray[math.floor(size)] or 0) + 1
end

local str = ""
for _, value in TestArray do
	str ..= #str > 0 and `, {value}` or value
end
print(`range: {str}`)

for i, v in pairs(TestArray) do
	table.insert(chances, (v / TryAmount) * 1e2)
end

local minChance, maxChance, meanChance = calculateMinMaxMean(chances)
local medianChance = calculateMedian(chances)
local modeChance = calculateMode(chances)

print(string.format("Minimum Chance: %.4f%%", minChance))
print(string.format("Maximum Chance: %.4f%%", maxChance))
print(string.format("Mean (Average) Chance: %.4f%%", meanChance))
print(string.format("Median Chance: %.4f%%", medianChance))
print(string.format("Mode Chance: %.4f%%", modeChance))

--[[
output for 0.05 lambda:

 16:03:58.254  range: 48, 46, 42, 45, 41, 43, 31, 34, 41, 31, 34, 20, 24, 18, 24, 13, 20, 28, 20, 29, 16, 17, 11, 13, 18, 15, 15, 10, 19, 13, 11, 12, 12, 3, 8, 5, 7, 10, 8, 7, 7, 4, 3, 6, 5, 7, 6, 3, 8, 2, 5, 6, 4, 2, 5, 1, 3, 3, 2, 2, 3, 4, 2, 2, 1, 5, 2, 3, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  -  Server - Script:81
  16:03:58.254  Minimum Chance: 0.1000%  -  Server - Script:91
  16:03:58.254  Maximum Chance: 4.8000%  -  Server - Script:92
  16:03:58.254  Mean (Average) Chance: 1.0870%  -  Server - Script:93
  16:03:58.254  Median Chance: 0.5000%  -  Server - Script:94
  16:03:58.254  Mode Chance: 0.1000%  -  Server - Script:95
]]

image

this ExponentialBiased function, by increasing the number value of “lambda” will make higher potential to return the smaller numbers over bigger numbers, but decreasing the lambda will make higher potential to return bigger numbers over smaller numbers.
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.