How do I make this "colors array to string" code faster?

So, I have a function which returns a string of RGB values with separators given from an array of Color3 values. All though my current method is very slow as it uses lots of string concatenations.

How can I fix this?

local function ConvertColoursToListString(Colours)
	local ColoursListString = ""

	for i, Colour in pairs(Colours) do
		local ColourR = math.floor(Colour.R * 255)
		local ColourG = math.floor(Colour.G * 255)
		local ColourB = math.floor(Colour.B * 255)

		local StringSegment = tostring(ColourR) .. "," .. tostring(ColourG) .. "," .. tostring(ColourB)
		ColoursListString = ColoursListString .. StringSegment
		
		if i ~= #Colours then
			ColoursListString = ColoursListString .. "S"
		end
	end

	return ColoursListString
end

If a string is not required for what you are trying to accomplish, you could replace the string with a table instead.

Thing is, the string is required as it is the purpose of the function. To convert a given color3 array into a string.

Example:

Colours = {Color3.new(1, 0, 0), Color3.new(1, 1, 1), Color3.new(0, 0, 0)}

Output:

"1,0,0S1,1,1S0,0,0"

If you are willing to create a table, this topic can help you convert it into a string efficiently. However, elements are separated by spaces, which may not be what you are looking for.

table.concat wont work in this situation because the code needs two sets of seperators. One seperator for each of the R B G values, and another seperator to seperate the R G B text

"1,0,0S1,1,1S0,0,0"
-- , and S are seperators here

you don’t really need to use tostring(), you could just apply the numbers onto the string and have the same thing

Removing tostring actually seemed to have slowed the function down by about 30%, which I did not expect

using something similar to what @Fizzitix said you can use something like this.

local function Convert(Colours)
	local ColoursListTable = {}
	
	for i, Colour in ipairs(Colours) do
		if i == 1 or i % 3 == 0 and i ~= #Colours then
			table.insert(ColoursListTable, Colour.R * 255)
		end
		table.insert(ColoursListTable, Colour.G * 255)
		table.insert(ColoursListTable, tostring(Colour.B * 255)..(i ~= #Colours and "S"..tostring(Colours[i + 1].R * 255) or ""))
	end
	return table.concat(ColoursListTable, ",")
end
2 Likes

I heard using table.concat was faster than manual concatenation, but if you don’t want to do that you could probably do something involving formatting and repetition.

In Luau ipairs is faster than pairs as well. If you’re using normal Lua or some other version of Lua the performance might be worse though. In that case you could use a generic for loop.

true is outputting whether or not the strings are equal
image

Here’s what I was able to come up with:

-- micro optimization because indexing technically takes time

local round = math.round
local len = string.len
local sub = string.sub
local insert = table.insert
local format = string.format
local rep = string.rep

local RGB_STRING = '%d,%d,%dS'
local function coloursToListString(colours)
	local formatString = rep(RGB_STRING, #colours)
	local colourData = {}
	for i, colour in ipairs(colours) do
		local r,g,b = round(colour.R * 255), round(colour.G * 255), round(colour.B * 255)
		insert(colourData, r) -- table.insert(t, element) is faster than 't[#t + 1] = element' in Luau
		insert(colourData, g) 
		insert(colourData, b)
	end
	return format(sub(formatString, 1, len(formatString) - 1) --[[trim off last S]], unpack(colourData))
end
Testing code (note I switched out your math.floor with math.round
-- micro optimization because indexing technically takes time
local round = math.round
local len = string.len
local sub = string.sub
local insert = table.insert
local format = string.format
local rep = string.rep

local RGB_STRING = '%d,%d,%dS'
local function coloursToListString(colours)
	local formatString = rep(RGB_STRING, #colours)
	local colourData = {}
	for i, colour in ipairs(colours) do
		local r,g,b = round(colour.R * 255), round(colour.G * 255), round(colour.B * 255)
		insert(colourData, r) -- table.insert(t, element) is faster than 't[#t + 1] = element' in Luau
		insert(colourData, g) 
		insert(colourData, b)
	end
	return format(sub(formatString, 1, len(formatString) - 1) --[[trim off last S]], unpack(colourData))
end

local function ConvertColoursToListString(Colours)
	local ColoursListString = ""

	for i, Colour in pairs(Colours) do
		local ColourR = math.round(Colour.R * 255)
		local ColourG = math.round(Colour.G * 255)
		local ColourB = math.round(Colour.B * 255)

		local StringSegment = tostring(ColourR) .. "," .. tostring(ColourG) .. "," .. tostring(ColourB)
		ColoursListString = ColoursListString .. StringSegment

		if i ~= #Colours then
			ColoursListString = ColoursListString .. "S"
		end
	end

	return ColoursListString
end

local t = {}
for i = 1, 100 do
	t[i] = Color3.new(math.random(), math.random(), math.random())
end

local now = os.clock()
local s = coloursToListString(t)
local _end = os.clock()
print('My method took', _end - now)

task.wait(1)

local now = os.clock()
local s2 = ConvertColoursToListString(t)
local _end = os.clock()
print('Your method took', _end - now)

print(s == s2)
1 Like

This method is very interesting, especially line 9. I don’t really know what’s going on there, but indeed it is very fast.

Never thought of replacing string concatenations with tables.

Many thanks!

Your code is definitely much faster by quite a bit, but table.unpack fails when inputting heaps of color3 values

image

string.format(string.sub(formatString, 1, #(formatString) - 1) --[[trim off last S]], unpack(colourData))

Oh that’s interesting, in that case I’d stick with table.concat as mentioned above, but maybe use string formatting instead of the .. concatenation operator

1 Like

I have tested your code and discovered that something is wrong with it

Sometimes you get an extra number in the colours string between the S seperators

local Colours = {Color3.new(1, 0, 0), Color3.new(0, 0, 0), Color3.new(0.2, 0.2, 0.2), Color3.new(0, 0, 0), Color3.new(1, 1, 1)}

Output:

255,0,0S0,0,0S51,51,51,51S0,0,0S255,255,255

I’d try formatting the rgb string first, then insert the formatted string to colourData, then after that return the concatenated table, so something like:

local RGB_STRING = '%d,%d,%d'
local function coloursToListString(colours)
	local colourData = {}
	for i, colour in ipairs(colours) do
		local r,g,b = math.round(colour.R * 255), math.round(colour.G * 255), math.round(colour.B * 255)
		table.insert(colourData, RGB_STRING:format(r,g,b))
	end
	return table.concat(colourData, 'S')
end
1 Like

Ah, that is a far simpler approach to what I was trying to do. I have never really used string formatting much before. Seems to be very useful

Thank you very much!

1 Like

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