An efficient way to randomize a list of maps or items

If you’re making a round-based game that requires items or maps randomizing then I have a little tips for you.

Let’s start this by preparing the essentials. I will be making an example of randomizing maps every round.

local storage = game:GetService("ReplicatedStorage")
local mapsfolder = storage.Maps
local maps = mapsfolder:GetChildren()

For the next step, have you ever thought or did something like this?

local function LoadMap(map)
    map.Parent = workspace
end

while true do
    local map = maps[math.random(1,#maps)] --  <== This?
    LoadMap(map)
end

Well, I’m not saying it does not work, but it’s not an efficient way to do it, and there is a reason to it.
Let’s try math.random(1,5) and look at the output.
image
See how many times the number 5 and 3 keep repeatedly appearing and the number 4 only appears twice?
This is why it’s not a good way to do it.

So what other methods are there that you can use? :thinking:

I will give you one method in billions of ways to do it.

Here’s my method

Let’s not use math.random to get an object from a table, but instead let’s shuffle the table!

math.randomseed(tick())

local nums = {1,2,3,4,5}
local gen = 0

while true do
    Shuffle(nums) --This function will mix the nums' variables up randomly.
    gen = gen + 1
    print("Generation: ",gen)
    for i=1,#nums do
        print(nums[i])
    end
    wait(1)
end

Pretty neat, right? Now let’s make ourselfs a shuffle function:

local function Shuffle(tabl)
    for i=1,#tabl-1 do
        local ran = math.random(i,#tabl)
        tabl[i],tabl[ran] = tabl[ran],tabl[i]
    end
end

Now that we have everything that we need, let’s test it out and look at the output:
image
Isn’t that just wonderful?
If we use this method to randomize maps for each round, all the maps will appear in order and no maps will be repeatedly appearing.

Let’s create a step variable to walk through the table and when it reaches the end of the table, the table will re-shuffle itself.

math.randomseed(tick())

local nums = {1,2,3,4,5}

local function Shuffle(tabl)
    for i=1,#tabl-1 do
        local ran = math.random(i,#tabl)
        tabl[i],tabl[ran] = tabl[ran],tabl[i]
    end
end

local step = 0

while true do
    step = step + 1
    print(nums[step])
    if step == #nums then
        Shuffle(nums)
        print("Table re-shuffled!")
        step = 0
    end
    wait(1)
end

image
And we’re finished! :wink: Have fun randomizing maps or items without getting too many duplicates.

48 Likes

I recommend posting the code directly to the forums rather than taking screenshots. It makes it easier for people to copy the code to try it out themselves. It also improves accessibility and searchability. Here is how to format code properly when posting it:

```lua
--Code goes here
/```

Without the /

16 Likes

I have another way to shuffle an array which I think is easier

local toShuffle = {"one", "two", "three", "four"}

local function shuffleArray(arr)
    local arrCopy = {unpack(arr)} -- # making copy of arr

    for i = 1, #arr do
        arr[i] = table.remove(arrCopy, math.random(#arrCopy))
    end
    return arr -- # arr has been shuffled, return back for convenience
end

print(table.concat(shuffleArray(toShuffle), ", "))
--> four, two, three, one
12 Likes

Copying the entire table to shuffle it is a gigantic overhead though, making it problematic if not impossible at very large tables. The actual result appears to be equal to OP, since both are re-assigning each index to a random new value.

9 Likes

As long as both use tables with numeric indices, both methods work fine regardless of the value held in a key (assuming the tables are shallow). Or did you mean something else?

I believe @emojipasta is referring to the fact that I used table.concat to print the elements of the now shuffled table. But that is not the point of my function, and I do agree with the large tables issue.

1 Like

For your code sample, why do you keep maps in ReplicatedStorage? I’m curious to know what your rationale for this is.

In terms of the thread though, shuffling a table for pseudorandomisation is something I never bothered to implement in “random-based” map modes (normally I use an anchored cycle), rather thought of. I never really have a reason to shuffle tables. Cool tip, though.

I’m pretty sure that you could just do something like

for i = 1, 5 do
    table.insert(nums, i)
end

to easier insert numbers into a table without having to insert numbers into the table again, although the efficiency of something like this would become, unless I am wrong, a concern.

It’s possible they’re attempting to reduce download sizes when switching maps by simply re-parenting on the client rather than keeping it strictly server-side. Or just for the example.

1 Like

Do a Fisher-Yates shuffle, like this one:

local rng = Random.new()

function shuffle<V>(source: { V }):{ V }
    for i = #source, 2, -1 do
        local j = rng:NextInteger(1, i)
        source[i], source[j] = source[j], source[i]
    end
    return source
end
54 Likes