How randomize dictionary using weights?

I have a dictionary with tree spawners:

local Spawners = {
	Spawner1 = 25,
	Spawner2 = 50,
	Spawner3 = 10,
}

How I can get 1 random spawner from this dictionary, if the higher Spawner Volume, the more chance it should have to become selected?
[Example: Spawner2 should have the highest chance to be seleted, but Spawner3 - the least]

2 Likes
table.sort(Spawners, function(a, b)
    return (a > b);
end);

print(Spawners[1]);

Sorting only works for arrays, he’s using a dictionary

Oh right, sorry didn’t notice that :sweat:

Hello @GamEditoPro

I Think you are asking this:

local list = {}
local chances = {
	Spawner1 = 25,
	Spawner2 = 50,
	Spawner3 = 10,
}

for thing, percentage in pairs(chances) do
	for i = 1, percentage do
		table.insert(list, thing)
	end
end
-----
local choice = list[math.random(#list)]

you can simply try this by putting for example 100 On a spawner

1 Like

That’s good, but 25, 50 and 10 was only for example. Actual Spawners Volume is around 1000000, and inserting 1000000 times 100 spawners isn’t best idea…

You are right this should be more Optimized

local sum = 0
local chances = {
	Spawner1 = 25,
	Spawner2 = 50,
	Spawner3 = 10,
}

for thing, chance in pairs(chances) do
	sum = sum + chance
end
local index = math.random() * sum
local choice

for thing, chance in pairs(chances) do
	index = index - chance
	if index <= 0 then
		choice = thing
		break
	end
end

print(choice)
1 Like

I tryed this variant before posting here - idk why, but pairs() almost 80% times gives the same combination of selected spawners (Ex: Sp2-Sp3-Sp1, Sp2-Sp3-Sp1, Sp2-Sp1-Sp3, Sp2-Sp3-Sp1, Sp2-Sp3-Sp1), and due to this even the smallest spawner can have the most trees. :slightly_frowning_face:

It can always be a coincidence, there are an infinite number of cases, You will never know if it is really correct, I am sure of my scripts because putting 100 always comes out that.

1 Like

You question is not right, you don’t want to randomize a dictionary but instead you want to do weighted randomness with a dictionary containing weights of every key.
Here is my solution to your problem:

local function weighted_random(weights)
        local sum = 0        
        
        for k, v in pairs(weights) do
                sum = sum + v                
        end     
        
        local rnd = math.random(0, sum)
        for k, v in pairs(weights) do
                if rnd < v then
                        return k
                end     
                rnd = rnd - v  
        end     
        error("Something went wrong!")
end

local Spawners = { 
        Spawner1 = 25,
        Spawner2 = 50,
        Spawner3 = 10,
}       

print(weighted_random(Spawners))

Also for people who knows that the sum of all the weights gonna be 100 for example:

local function weighted_random(weights)
        local rnd = math.random(0, 100)
        for k, v in pairs(weights) do
                if rnd < v then
                        return k
                end
                rnd = rnd - v
        end
        assert("Something went wrong!")
end

local spawners = {
        Spawner1 = 10,
        Spawner2 = 80,
        Spawner3 = 10, 
}

print(weighted_random(spawners))

I got the algorithm from here btw:

Have a good day <3

3 Likes

U sure this is not print?

2 Likes

That’s just a safe guard. that basically means the code should never reach that point or something went wrong somehow unexpected, but if the function is used properly, everything should be fine.
If somehow the function failed because of wrong data or usage somehow then it will error.

1 Like

Oh sorry, I just understood what you meant, I fixed it :slight_smile:

1 Like

By the way, you shouldn’t use assert for that since it mysteriously brings up huge script memory usage. You should use ifs.

2 Likes

I made it as an error, because assert in lua needs a condition while in C++ it will error as soon as it hits that line.

2 Likes

in your case it is as simple as doing

Spawners["Spawner" .. math.random(1, 3)]

I maked system like your. Now, it picks only 1 spawner all time, and spawns trees only on it.

local TotalWeight = 0
for Spawner, Weight in pairs(SpawnerObjects) do
	print(Spawner)
	print(Weight)
	TotalWeight = TotalWeight + Weight
end
local RandNum = math.random(0, TotalWeight)

for Spawner, Weight in pairs(SpawnerObjects) do
	if RandNum <= Weight then
		SelectedSpawner = Spawner
	else
		RandNum = RandNum - Weight
	end
end

image

It should be weight based. Char limit…

I FIND PROBLEM. After receiving RandomNum <= Weight, it was able to go lower than 0 (-214421.908241). After adding “break” it works like needed! Everyone thanks for help!

1 Like