Best way to create a string where it adds the numbers at the end by 1?

I want to know the best way to add the numbers at the end of a string by 1, if there is none, then it is concatenated and ended with 1.

local templateName = "Example212151513532"
        
local reverseString = string.reverse(templateName)
if string.find(reverseString, "%d", 1) == 1 then
     local matchLastNumbers = string.match(reverseString, "%d+", #reverseString)
     local numbers = string.reverse( matchLastNumbers )
     templateName = string.gsub( templateName, numbers, "", 1 ).. numbers + 1
else
     print("No numbers found at the end")
     templateName = templateName.."1"
end
        
print(templateName)

My goal is to be able to change the last numbers of a string to the full number it is plus 1. Thus I need "Example212151513532" to become "Example212151513533"

Would like to know what I’m doing wrong. Error: :6: invalid argument #1 to 'reverse' (string expected, got nil) which I know means that matchLastNumbers is somehow nil yet I was able to match a number at the end of the first string.

I also would like to know if this is the best way to do it or if I can fix it but obviously it’s not correct now so I’ll leave it to after being solved for a code review.

it seems the reason your code isn’t working is because you are matching the reversed string from the end, when you should be starting at the beginning instead. So removing #reverseString should fix the problem.


Even though the way your doing it works, i think the easier way to do this would be to something like this instead:

local Before, Number = templateName:match("(.-)(%d*)$")
Number = tonumber(Number) or 0


templateName = Before..(Number + 1)

Can you explain how this pattern works? I only know from this you’re using %d for number digits.

Also since Roblox automatically concatenates and does operations on string numbers & non-string numbers, it’s fine to cut out the tonumber part.

(.-)
Parenthesis are variables.
. matches any character
- tries to match the least amount of characters possible.

(%d*)$
Parenthesis are variables
%d matches numbers
* matches 0 or more
$ forces the string to match the end

1 Like

I get what they do but not exactly how they sync into one pattern to work with each other.

For example, (.-) looks to me to match the least amount of any character, then it’d try to match only one character to aim for lowest non-zero? It’s hard to know based on your reply on how they work in combination and as variables within the parenthesis together separately.

local result
for num in string.gmatch(templateName, "%d+") do
    result = num
end
local add = templateName:sub(1, templateName:len()-result:len())..tostring(tonumber(result)+1)

This works with strings like “abc12345abc999” > “abc12345abc1000”
Step by step of what the script is doing:

local templateName = "abc12345abc999"
local result --create a undefined variable
for num in string.gmatch(templateName, "%d+") do --string.gmatch results an iterator function that we can 'iterate' through
result = num --we're trying to get the last string of numbers, so we just update the result by whatever number we get
end
--templateName:sub(1, templateName:len()-result:len()) gets the string without the '999'
--tostring(tonumber(result)+1) turns a string '999' into a number, adds 1,then turns it back into '1000'
--local add = thing1..thing2 combines the 2 and turns it into 'abc12345abc1000'
local add = templateName:sub(1, templateName:len()-result:len())..tostring(tonumber(result)+1)

What happened to explaining the pattern?

(.-) by default will match nothing, but since there is an anchor at the end of the pattern, $, it is forced to match all the non numerical characters.

Wouldn’t only (%d*)$ work? What is the significance of (.-) with that pattern?

I assume that (%d*) matches 0 or more number digits then the $ forces it to match towards the end of the string but then what about (.-)? Does $ force the string to match the last character for the pattern to work?

(.-) returns Before, which is also necessary when combining Before and Number.

What do you mean by “Before”?

@Ailore got it spot on, the $ symbol/anchor in the pattern will force the string to matched starting from the end. Then this: (%d*) with the * modifier will match 0 or more following digits, while also being captured using the (). The (.-) then will match and capture as few preceding characters as possible (while still matching backwards) so that it can be used as the Before variable/the part that needs to be append back to the newly formed number. So in this case (.-) is capturing the remainder of the string.

Before = “Example”

Number = 212151513532


An example of the - modifier could be something like:

print(string.match("abc123", "(%l-)%d+"))  --abc

a, cand b are the fewest preceding lowercase letters possible needed to find the match. The - modifier is essentially the opposite of the * modifier, which finds the most proceeding character classes. However in the scenario above both the fewest and most possible character class matches turn out to be abc so using * or - here wouldn’t make a difference. One time using - over * would matter though (there are many more), is when let’s say you wanted to have some sort of pattern that needs to return any set of characters that are between any size sequence of numbers:

local String = "12345#Hello!321#World!12"

for SubString in string.gmatch(String, "%d+(.-)%d") do
	print(SubString) -->> #Hello! , #World!
end

Now using (.*) instead of (.-) :

...
 print(SubString) -->> #Hello!321#World!1, #Hello!321#World!1

(.*) expands as far as possible, where as (.-) stops once it doesnt need to “expand” further and the least needed characters are found.


Those examples probably weren’t that good so perhaps this will be of better help:
https://www.lua.org/pil/20.2.html


Also yeah, i only included tonumber for readability, so it’s probably fine if you leave it out in replace of automatic coercion since you are only doing simple arithmetic operations.

3 Likes
local str = "Example212151513532"

local function incrementNumAtEnd(s)
   return string.format("%s%d", s:match("%a+"), (s:match("%d+") or 0) + 1) -- you could just concatenate and make life easier too,  s:match("%a+") .. s:match("%d+") + 1
end

print(incrementNumAtEnd(str)) --> Example212151513533
print(incrementNumAtEnd("abc")) --> abc1
print(incrementNumAtEnd("abc11")) --> abc12

string.match returns the part of the string it matched within the string, use that to separate letters and numbers, then add 0 if no match was found for the digit character class and concatenate it back with the letter part.

Edit: added brackets, see 2nd post below this to see why.

By the way your function doesn’t work.
image

@Jaycbee05 It’s still confusing, this for example: print(string.match("abc123", "(%l-)%d+")) which results in “abc”, I know exactly what the patterns are when involved with % like %d and %l-, just not how they work in synchronization to create a full pattern. For example, what does (%l-) have to do as a variable while being next to %d+? I’ve used + before so I know it tries to achieve 1 or as many matches as it can find.

I could probably understand what individually they do, just not how they work together. To me, reading this part "%d+(.-)%d" just looks like this in my view:

  1. Find 1 or more digit matches (%d+)
  2. Find least amount of any characters???
  3. Random %d that I have no idea does what

Also, I did not know what Before was, does string.match() return two variables or something? Is Before (first variable) the match and the second variable supposed to be how many matches were found?

EDIT: Your new function now works correctly. Tested in output.

Yeah all I missed was a bracket quite literally, should be fixed now as it really was just pseudo-code.

The thing is, without the brackets in this case the or keyword would’ve let either a truthy value or 0 +1 (= 1) get through each time, now it’ll cause it to increment the match if found (if not then 0) , by 1 and pass that for use.


It’s on the Roblox DeveloperHub too by the way.

You can’t return variables so I’m confused, you probably meant values?

As I mentioned in my previous reply, string.match returns a match if found, otherwise nil.

The (%l-) needs to be in front of %d+ because the first thing we are looking for is a lowercase letter, the - tells us that we only need to match %l as few times as possible until we find a %d occurrence. it happens to be that with "abc123" as the input, if we were using either *, + , we would still get the same result of "abc", because both of those modifiers (attached to their character class) are looking for the most possible lowercase letters, which would indeed be abc. The difference between * and + is that the + modifier requires that at least 1 occurence of the character class is found for the string to be matched at all.

if we were to change up the input string to something like: "123abc123" we would end getting a empty string(""), because we are explicitly signifying with the - modifier that we want to match as few lowercase letters as possible to get to the first digit in the string(find the first match of %d+). Because the string starts with 123 we don’t need to match any lower case letters at all, and our capture gives us nothing ("") back.


The first thing the "%d+(.-)%d" pattern does is find the first occurrence of a digit then it matches any preceding digits ( %d+). After we have found this first occurrence of digits we want to match and capture any characters necessary to get to the next occurrence of the %d character class. So %d is important in that without it, we wouldn’t need to capture/match any preceding characters, because as we’ve specified we only want to match and capture what’s necessary, “preferably none”.


Regarding capturing (which is covered on the dev hub), String.match will return the overall match, unless we throw in a capture which is denoted using the () wrapped around a character class. When we tell it to capture something it will “save” what was found, and give us back the result.

local String = "Array : 5, 2, 3"
print(string.match(String,"%a+%s?%:%s?(%d+)%s?%,%s?(%d+)%s?,%s?(%d+)" )) -- 5 2 3

"%a+%s?%:%s?(%d+)%s?%,%s?(%d+)%s?,%s?(%d+)" is a pretty long pattern, mainly because of all of the %s?, which are meant to optionally match any whitespace characters. So, Because String.match allows us to “return” things found from within captures, we can actually use string.split to shorten some things instead:

local String = "Array : 4, 10, 3, 15"
print(table.unpack(String:match("%a+%s?%:(.+)"):split(","))) --4  10  3  15

5 Likes

What does : do here btw?


This part is somewhat confusing, what does it mean to capture any characters necessary until the next occurrence? What determines that you’ll go to the next occurrence? Is it the character pattern that is after what you enclosed in () to look for next? Would %d+ scan for the most digits if existing then whatever you had after will continue until the end or something like that?

If this feels dragged on, I can do private messaging to resolve the issue.

: serves no special purpose , it’s only there because it’s in the string, %: is looking specifically for :


i guess it would be good to think about patterns from left to right, What i mean by capturing until the next occurrence of %d in the context of the %d+(.-)%d pattern, is that after we match the first set of digits (a contiguous set), this could be a number of any length (1 or 123, 54321 etc.). The next step (.-) is to match any characters that are absolutely necessary to get to another digit (a member of the %d class).

As an example, if i have a string like: !!!321f!!!232! and i wanted to get the characters 321f, i would want to start at the 3rd exclamation mark and then get all the characters/numbers there are up until the 4th exclamation mark. To do that i could first have a character class that looks like %!+, using that just as a pattern will look for the first contiguous set of exclamation marks:

string.match("!!!321f!!!232!", "%!+") -->> !!!

now what i want to do is get any numbers or characters we can find until we find another ! in the string. This can be done using the . class and using the - modifier, using something like: .- along with %! will match any character until a ! is matched (found):

print(string.match("321f!!!", ".-%!")) -->>321f!

To just get the 321f back we can use a capture to grab only the characters we found up until the !, and not the characters and the !that we matched.

print(string.match("321f!!!", "(.-)%!")) -->>321f

So now that we know how to find just the exclamation marks until another character is found (a contiguous set) and how to find all characters up until a !we can combine them together to get desired results:

print(string.match("!!!321f!!!232!", "%!+(.-)%!")) -->>321f

And yes, if you still need help, pms can work. Although this:

probably does a better job of explaining than i am doing.

3 Likes