Simpler way to generate none-repetitive integers

Here is my code for generating 3 random non-repeating integers, but if I want to add more non-repeating integers it becomes more and more complicated. Are there any ways that I can generate a specific number of integers using less codes? (Other than that if there’s anything I can simplify please tell me too, bc am noob)

task.wait()

local maps = game.ReplicatedStorage.Maps:GetChildren()

local mapNum = 0
for index, thing in ipairs(maps) do
	mapNum += 1
end

local l1 = math.random(1,mapNum)
local l2 = math.random(1,mapNum)
local l3 = math.random(1,mapNum)
while l2 == l1 do
	l2 = math.random(1,mapNum)
end
while l3 == l1 or l3 == l2 do
	l3 = math.random(1,mapNum)
end
1 Like

Edit: @steven4547466 has a much better method, they fixed a flaw in mine that I hadn’t spotted.

Your method of using a while loop to check if they are the same is good, and we will keep that part. However, the rest of it all needs to be in a loop too, so we can have a sequence of whatever length we like. I would also suggest putting it in a function (it’s the sort of thing that gets more than once). First off, we can define our function and it’s parameters - we need a length, a minimum and a maximum - and the return format: a table. I’d also suggest using types (and therefore strict mode), but that’s up to you.

--!strict
local function UniqueNumberSequence(Length: number?, Minimum: number?, Maximum: number?): {number}
	
end

Then we need the defaults for the parameters, these can be whatever you want but I’ll use 2, 0 and 5 respectively.

Length = Length or 2
Minimum = Minimum or 0
Maximum = Maximum or 5

Next up is our internal variables, we only need one - to put the return sequence in. We will use table.create() since it is easier on the computer to assign memory space and we know the exact length of the table. Length also needs a type assertion (since it can be nil but we know it isn’t)

local Sequence: {number} = table.create(Length :: number)

Now we need a loop to go over the whole length of the sequence. Length needs another type assertion.

for Index: number = 1, Length :: number do
	
end

To calculate the numbers, we need to choose a random value:

local RandomNumber: number = math.random(Minimum, Maximum)

Then, we need to compare it with the other values that are already in the sequence and change it if that number has already appeared. This can be done nice and easily using table.find() and a while loop (just like what you already have).

while table.find(Sequence, RandomNumber) do
	RandomNumber = math.random(Minimum, Maximum)
end

After that loop, we know that the number must be unique so we can add it to the Sequence table.

Sequence[Index] = RandomNumber

Excellent! We now have all the code required to generate a sequence of unique numbers, all we need to do is return what we calculated:

return Sequence

Final Code:

--!strict
local function UniqueNumberSequence(Length: number?, Minimum: number?, Maximum: number?): {number}
	Length = Length or 2
	Minimum = Minimum or 0
	Maximum = Maximum or 5
	
	local Sequence: {number} = table.create(Length :: number)
	
	for Index: number = 1, Length :: number do
		local RandomNumber: number = math.random(Minimum, Maximum)

		while table.find(Sequence, RandomNumber) do
			RandomNumber = math.random(Minimum, Maximum)
		end

		Sequence[Index] = RandomNumber
	end
	return Sequence
end
1 Like

This is one way to achieve that. It is efficient and is often used a lot in Roblox.

local n = 5 -- how many numbers will be generated
local r = {} --here the generated numbers will be stored
local t
repeat
	t = math.random(1,20) -- your number range
	if not r[t] then
		r[t] = true
		n -= 1
	end
until n<=0

for i in pairs(r) do  -- random numbers are in the keys (for efficiency reasons)
	print(i)
end

There are also generators with which you can generate infinite numbers, but this is an advanced topic.

While I would say this code is significantly better than the OP, I personally wouldn’t use a while loop to do this as you might not ever get the number you need leading to hangs forever, and if Length > Maximum the code will halt forever and cause this error:

--!strict
local function UniqueNumberSequence(Length: number?, Minimum: number?, Maximum: number?): {number}
	Length = Length or 2
	Minimum = Minimum or 0
	Maximum = Maximum or 5
	
	assert((Maximum :: number - Minimum :: number + 1) >= Length :: number, "Range must be greater than Length.")

	local Sequence: {number} = table.create(Length :: number)

	local ToChoose: {number} = table.create(Maximum :: number - Minimum :: number + 1)
	
	for i: number = Minimum :: number, Maximum :: number do
		table.insert(ToChoose, i)
	end
	
	for i: number = 1, Length :: number do
		local index: number = math.random(1, #ToChoose)
		table.insert(Sequence, ToChoose[index])
		table.remove(ToChoose, index)
	end
	
	return Sequence
end

Using this means you always have (Maximum - Minimum + 1) + Length iterations and ensures your length is not greater than your maximum. Using a fully random system can go forever if the distance between Maximum and Minimum is high enough.

Excellent observation. But if you want to generate n different numbers, you must also provide enough range to generate the n numbers. Otherwise it would be a logic error that cannot be patched in a generic way (each implementation should take its own actions to deal with this).

True, I actually had an oversight in my assert, which I have just fixed as well since you pointed that out. Maximum doesn’t need to be greater than Length, the value of Max - Min + 1 must be greater than or equal to length, that would ensure you have Length numbers to choose from, at least.

I’m just saying, math.random is pretty bad… Use a RandomObject.

local RNG = Random.new() -- The internal seeds are better unless you want repeatability.

for I = 1,100 do
  print(RNG:NextInteger(0,100))
end

-- Other methods to note:
-- RNG:NextNumber(min,max) Next float (with decimals) from range. 0/1 default min/max
-- RNG:NextUnitVector() Randomly oriented unit vector, uniformly maps over unit sphere.
-- As for the vector and its use case, perhaps randomly biasing ricochet? Idk.

What’s wrong with it? They both have an average of halfway between the minimum and maximum values and math.random() is significantly faster. I benchmarked it with:

local RNG = Random.new(tick())
local Repeats: number = 10000000

local MTotal: number = 0
local MStart: number = os.clock()

for Index: number = 1, Repeats do
	MTotal += math.random(0, 1)
end
print(string.format(Repeats.." iterations of math.random(0, 1) took %.3f ms, and had an average of", (os.clock() - MStart) * 1000), MTotal / Repeats)

local RTotal: number = 0
local RStart: number = os.clock()

for Index: number = 1, Repeats do
	RTotal += RNG:NextInteger(0, 1)
end
print(string.format(Repeats.." iterations of RNG:NextInteger(0, 1) took %.3f ms, and had an average of", (os.clock() - RStart) * 1000), RTotal / Repeats)

and got the following:

17:28:51.448  10000000 iterations of math.random(0, 1) took 448.274 ms, and had an average of 0.5000002  -  Studio
17:28:52.052  10000000 iterations of RNG:NextInteger(0, 1) took 604.586 ms, and had an average of 0.4999752  -  Studio