Adding string.at(str, n) to directly index a character in string

As a developer, I find myself doing string.sub(str, n, n) way too often when I just want to get one character in the string, which is tedious. It would be nice if there was a shorthand built-in method for this where I only pass the position ‘n’ once, so that it’s easier to write, and easier to tell what I’m actually doing. I propose the following options:

local character1 = string.at(str, n) -- gets the n-th character in the string
local character2 = str:at(n) -- alternative way of calling

-- OR:

local character = str[n] -- indexing the string as an array

I think the first option makes most sense to implement out of the two options, for api consistency and also because str:at(n) makes it more obvious that str is a string and that you are indexing at the n-th character, while for the second option str[n], you’re not sure whether str is a table or a string just from looking at that line.

Another example where this is useful in simplifying code:

-- For cleaner code and/or being more performant, have to make variable:
local longExpression = 1 + 2*expensiveFunction() + 42*i / 5
local char = str:sub(longExpression, longExpression)

If this feature is implemented, you could just do:

local char = str:at(1 + 2*expensiveFunction() + 42*i / 5)

It saves space, makes it clearer what I’m doing, and assists me in avoiding less performing situations (i.e. writing the same expression twice in string.sub without precomputing first).

22 Likes

str:at(n) would be handy. str[n], however, goes beyond the boundaries of Lua.

8 Likes

I don’t think so – they could modify string’s underlying metatable so that __index returns the character when the string is indexed with an integer. That wouldn’t be stock Lua anymore, but it’s not “beyond the boundaries”.

That being said, I do prefer the first option I gave anyway and it would be less hacky to implement, I’m just giving both options here to be complete.

3 Likes

I don’t find this function really all that useful. The string.sub method is hardly tedious.
This would just be bloat to the string library.
Just store any computations in a variable. (Doing as you suggested just breaks readability)

4 Likes

Thanks for adding your opinion. I disagree personally, I think doing str:sub(n, n) is an obscure way to get the n-th character in a string compared to string operators in other scripting/programming languages, so I would say that the string.at approach improves the code. I personally don’t see how my suggestion would “break” readability for that reason.

2 Likes

Break might be a too exaggerated word but more like reduces because variables help detail why you need to due a computation for what you want in the first place and helps isolate the arithmetic.

Support, I do string.sub(str, n, n) way too much.

4 Likes

Strings have underlying metatables? Wut? 4 real?

You can’t manipulate them inside your Lua code (they are read-only), but yes.

1 Like

I don’t think string.at should be added to the string global. First off, you should be using or at least supporting/considering utf8, which doesn’t always play nice wtth sub. Secondly, getting the nth character from a string is most likely your workaround for not bothering to use either string patterns or utf8.codes. Your example shows some complex mechanism to determine what character to get but frankly the reason this is awkward is because you shouldn’t do it.

Strings shouldn’t be your datatype of choice for representing anything that isn’t a string: you can’t really mutate them efficiently because every string you make is hashed and whatnot. You probably shouldn’t store big strings in datastores nor keep them like that for long after fetching them.

For your usecase in the OP, I’d suggest you just do it yourself and either add your trivial utility wrapper function to a string global or make your own btString library or a local function or whatever.
It’s literally a oneliner:

local function StringAt(str, at) return str:sub(at, at) end

Could you please post an actual concrete example of when you really need the nth character from a string?

2 Likes

To add on to what buildthomas said, any data in Lua can have a metatable. Most types require the debug library to access, which has been removed from Rbx.Lua. The string metatable is what allows you to use the string library functions as methods of strings.

("hello world!"):sub(2,5)

Someone correct me if I’m wrong, but I believe that types like numbers and strings have one shared metatable unlike tables and userdata.

1 Like

If I’m doing string.sub, I don’t care about UTF8 characters to begin with (as you point out), so I also wouldn’t care about UTF8 when I’m using this suggested feature.

I use plenty of string patterns and I parse using utf8.codes when I have to and when it is convenient. Some things are easier solved when you just get a certain character from a string instead. I will edit in a list of use cases where getting a character in a string is useful when I have time to compile it, thank you for the suggestion.

I don’t really know how to respond to this, this seems like it has little to do with this thread.

If you want to argue about whether strings are a bad/good choice for storing data, you are welcome to do so in a thread on its own. My use case for this has nothing to do with storing strings, or performance when processing long strings, I’m just trying to do some processing on strings where it is convenient to get a single character at some index.

2 Likes

If indexing strings is implemented it should return the byte, not the character string, it would be mathematically way more useful when storing and unpacking data. I think string.sub is fine, my code base is over 100k lines and I use string.byte much more often than string.sub, which doesn’t require typing your variable twice to get a single byte.

4 Likes

Really good point. In that case, it might even be useful to have both of the options I list, with the second one (str[n]) returning the byte and the first option returning the character. Based on the responses here it’s somewhat opinionated whether the first option is needed (it is just a shortcut + making it just a bit clearer what is going on, but I would use it pretty often), but we’ll see how they prioritise it I suppose.

I would love str[n].
But, it would be a hacky-nightmare.
Personally, the more we treat strings as arrays the more we may promote some bad practices to those who maybe haven’t bothered to fully learn string manipulation and patterns.

The idea of :at() sounds nice but I do think utf+ is a problem. In fairness it is also pertains to using :sub() as well.
I also believe there aren’t enough use-cases that would occur in one game to justify a need to truncate :sub().

plus i dont wanna go back and redo all my “deprecated” code with :sub(n,n) because OCD compells it

FULL SUPPORT LETS BE C++ INSTEAD OF JAVA AND HAVE STRINGS BE CHAR ARRAYS NOT SOME WEIRD BINARY THINGS!!!

I’m talking about the (I think it’s called) random access idea.

In Java we would do str.charAt(n), I’m surprised there’s nothing like this in Lua.

1 Like

That’d be str:sub(n,n) or, since in Java it’s just an integer that’s called a char: str:byte(n)

cuz i can:

string.char(str:byte(n))
str:sub(n):sub(1,1)

inefficient and stupid, but only uses n once ¯\_(ツ)_/¯

1 Like

As much as it could easily be done, str[n] would question the definition of a string compared to that of a table. It just wouldn’t work. Although you could spend time making a system like that easily.