Coding Challenge 2020-05-19: Number formatting and abbreviation

This is a coding challenge inspired bt @starmaq’s Coding Challenge
The reason it’s here it’s because

Here’s the reqiurement:

  • Has to work on Roblox Studio so it has to be written in Lua 5.1 and you can’t use features vanilla Lua has but Roblox Lua disabled
  • No HttpService

You are given a data to create a function that formats and abbreviates the number using that data. The data is a table

{
	decimalSymbol = ',',
	groupSymbol = ' ',
	negativeSymbol = '-',
	groupSize = { 3 },
	minimumGroupingDigits = 2,
	compact = { '0', '0', '0', '0', '0', '0', '0 M', '00 M', '000 M', '0 B', '00 B', '000 B', '0 T', '00 T', '000 T' }
}
key Meaning
decimalSymbol The decimal symbol
groupSymbol The digit grouping symbol
negativeSymbol The negative sign symbol on negative numbers
groupSize The size of grouping, { 3 } means group every digit { 3, 2 } means group the last 3 then every 2 digits (e.g. 12,34,56,789).

minimumGroupingDigits:
A value that controls what number can be grouped, it cannot be 0, it’s this is infinity, don’t group the number at all.

minimumGroupingDigits index 1 of grouping size value output
1 3 1000 1,000
2 3 1000 1000
1 3 10000 10,000
2 3 10000 10,000
3 3 10000 10000
3 3 100000 100,000
1 4 10000 1,0000
1 4 100000 10,0000
2 4 10000 10000
2 4 100000 10,0000
inf 3 1234567890 1234567890

compact:
The table of abbreviated string

  • The minimumGroupingDigits is always 2 if the minimumGroupingDigit normally is 1 if you’re abbreviating numbers, no exceptions
  • Just 0 means don’t abbreviate the number
  • The amount of 0 is the size of the group (e.g. 12345 on 00K will be 12K)
  • Ignore the single quote, e.g. 12345 on 00K'.' will be 12K., but '' will be '
  • Group the abbreviated number too e.g. (12,345T instead of 12345T)
  • The decimal size must be zero if the size is bigger than 1 (e.g. 12K instead of 12.3K), and the decimal size must be one if the size is 1 and it’s not zero (e.g. 1.2K instead of 1K but 1K instead of 1.0K)

There are no holding hands, I can only help if you don’t get it or the rules.
Boilerplate:

local data =
{
	{
		decimalSymbol = ',',
		groupSymbol = ' ',
		negativeSymbol = '-',
		groupSize = { 3 },
		minimumGroupingDigits = 2,
		compact = { '0', '0', '0', '0', '0', '0', '0 M', '00 M', '000 M', '0 B', '00 B', '000 B', '0 T', '00 T', '000 T' }
	},
	{
		decimalSymbol = ' decimal ',
		groupSymbol = ' group ',
		negativeSymbol = '-',
		groupSize = { 3, 2 },
		minimumGroupingDigits = 1,
		compact = { '0', '0', '0', '0', 'TestA 0', 'TestA 00', 'TestB 000', '0 TestC', 'TestC 00 ttest', '000 TestC' }
	},
	{
		decimalSymbol = '.',
		groupSymbol = ',',
		negativeSymbol = '-',
		groupSize = { 3 },
		minimumGroupingDigits = 1,
		compact = { '0', '0', '0', '0', '0万', '00万', '000万', '0000万', '0億', '00億', '000億', '0000億', '0兆', '00兆', '000兆' }
	},
	{
		decimalSymbol = ',',
		groupSymbol = '.',
		negativeSymbol = '-',
		groupSize = { 3 },
		minimumGroupingDigits = 1,
		compact = { '0', '0', '0', '0', '0', '0', '0 Mio.', '0 Mio.', '0 Mio.', '0 Mrd.', '00 Mrd.', '000 Mrd.', '0 Bio.', '00 Bio.', '000 Bio.' }
	},
	{
		decimalSymbol = ',',
		groupSymbol = '.',
		negativeSymbol = '-',
		groupSize = { 3 },
		minimumGroupingDigits = 2,
		compact = { '0', '0', '0', '0', '0', '0', '0 M', '00 M', '000 M', '0000 M', '00 MRD', '000 MRD', '0 B', '00 B', '000 B' }
	},
	{
		decimalSymbol = '.',
		groupSymbol = ',',
		negativeSymbol = '-',
		groupSize = { 3 },
		minimumGroupingDigits = 1,
		compact = { '0', '0', '0', '0K', '00K', '000K', '0M', '00M', '000M', '0B', '00B', '000B', '0T', '00T', '000T' };
	}
}
local examples = { 1500, 23456, 5343442, 865849384, 12345000000000000 }
function FormatNumber(value, data)
	-- Your code here
end;

function AbbreviateNumber(value, data)
	-- Your code here
end;

for _, d in ipairs(data) do
	for _, v in ipairs(examples) do
		print(FormatNumber(v, d))
		print(AbbreviateNumber(v, d))
	end
end

Expected output
Data 1:

1500
1500
23 456
23 456
5 343 442
5,3 M
865 849 384
865 M
12 345 000 000 000 000
12 345 T

Data 2:

1 group 500
1500
23 group 456
TestA 2 decimal 3
53 group 43 group 442
TestB 534
86 group 58 group 49 group 384
TestC 86 ttest
12 group 34 group 50 group 00 group 00 group 00 group 00 group 000
12 group 34 group 50 group 00 group 000 TestC

Data 3:

1,500
1500
23,456
2.3万
5,343,442
543万
865,849,384
8.6億
12,345,000,000,000,000
12,345兆

Data 4:

1.500
1500
23.456
23.456
5.343.442
5,3 Mio.
865.849.384
865 Mio.
12.345.000.000.000.000
12.345 Bio.

Data 5:

1500
1500
23.456
23.456
5.343.442
5,3 M
865.849.384
865 M
12.345.000.000.000.000
12.345 B

Data 6:

1,500
1.5K
23K
23K
5,343,442
5.3M
865,849,384
865M
12,345,000,000,000,000
12,345T

Good luck!

Answer, don't spoil yourself

1> Do the formatting number first. Convert number to string first, use :format instead of tostring, as because tostring will also include expoentents, which we don’t want here.
2> Create the parts, negative, integer, decimal, fraction
3> Format the integer
4> The return the value, replace - with the negative symbol and . (doesn’t matter literal dot or not because . means any charcter, and the decimal can only be `` or .) to a decimal symbol to ensure it’s actually there, and not empty
5> Create minimumGroupingDigit override, just in case

function FormatNumber(value, data, mgd_override)
	if type(value) == "number" then
		value = ("%.0f"):format(value);
	end;
	local neg, int, dec, frac = value:match("(-?)(%d*)([.]?)(%d*)");
	if #int >= data.groupSize[1] + (mgd_override or data.minimumGroupingDigits) then
		-- This is the grouping part
		local ret = '';
		local i = 1;
		while #int > data.groupSize[i] do
			ret = data.groupSymbol .. value:sub(-data.groupSize[i]) .. ret;
			int = int:sub(1, -(data.groupSize[i] + 1));
			if i < #data.groupSize then
				i = i + 1
			end;
		end;
		int = int .. ret;
	end;
	return neg:gsub('-', data.negativeSymbol) .. int .. dec:gsub('.', data.decimalSymbol) .. frac;
end;

5> Once your done, abbreviate the number, check if the value is 0 because the minimumGroupingDigits must be ≥2, use math.max to get the highest value
6> FormatNumber with minimumGroupingDigit overrided to ≥2
7> Use string.sub/divide numbers to get parts of the number.
8> Use string:gsub() to find how many zeros are there, do note that subtract length of value to length of the data.compact size with max at 0 just in case there are more
9> Add a full stop (decimal separator) with the next part of the number if the size is 1

function AbbreviateNumber(value, data)
	if type(value) == "number" then
		value = ("%.0f"):format(value);
	end;
	local cformat = data.compact[math.min(#value, #data.compact)];
	if cformat == '0' or cformat == '' then
		return FormatNumber(value, data, math.max(data.minimumGroupingDigits, 2));
	else
		local _, csize = cformat:gsub('0', '');
		csize = csize + math.max(#value - #data.compact, 0);
		local dsize = csize == 1 and 1 or 0;
		return (cformat:gsub(
			'0+',
			FormatNumber(value:sub(1, csize) .. (dsize == 0 and '' or '.' .. value:sub(csize + 1, csize + dsize)), data, math.max(data.minimumGroupingDigits, 2))
		):gsub("'([^']+)'", "%1"));
	end;
end;
8 Likes

Since this is a tutorial you are required to provide the answer upon posting the topic. You can see other coding challenge topics for examples of the right way to do this. You can just hide the answer under a Details tag.

Please edit your topic to comply.

1 Like