How to retrieve the last character of a string in ABAP – definitive edition
Many of the resources we have on ABAP might be old, but they still frequently show up on top positions of search results. I encourage you to correct incorrect information you stumble upon, even many years later.
Here is my attempt to thoroughly solve a trivial problem I needed reminding about this week – retrieving the last character of a string.
My analysis will be presented in the form of a contest between the respondents to the same question asked on SAP Q&A in 2007, with some measurements at the end. Let’s see how the answers fare in 2020. There were seven proposed solutions, but first…
the results. Because the rest is mostly rambling. If you’re interested in that, feel free to read the whole thing. But if you just want code and numbers, this is just a two-minute read:
I made some measurements of the correct approaches from the answers:
- rotate using SHIFT, return first character
The run times were be measured on a short string (guid) and a long string (5000 concatenated guids)
report zlastchar. class lcl_string_util definition create private. public section. " feel free to substitute your favorite single character abstraction types: t_last_character type c length 1. class-methods: get_using_substring importing str type string returning value(result) type t_last_character. class-methods: get_using_off_len importing str type string returning value(result) type t_last_character. class-methods get_using_shift importing value(str) type string returning value(result) type t_last_character. endclass. class lcl_profiler definition create private. public section. class-data: guid type string. class-data: n type i. class-methods: off_len, substring, shift. endclass. """""""""""""""""""""""""""""""""""" " we will measure a random short string as well as a much longer string lcl_profiler=>n = 10000. lcl_profiler=>guid = cl_system_uuid=>create_uuid_c36_static( ). data(really_long_string) = ``. do 5000 times. really_long_string = really_long_string && cl_system_uuid=>create_uuid_c36_static( ). enddo. lcl_profiler=>off_len( ). lcl_profiler=>substring( ). lcl_profiler=>shift( ). """""""""""""""""""""""""""""""""""" class lcl_string_util implementation. method get_using_substring. result = substring( off = strlen( str ) - 1 len = 1 val = str ). endmethod. method get_using_off_len. data(index_before_last) = strlen( str ) - 1. result = str+index_before_last(1). endmethod. method get_using_shift. shift str right by 1 places circular. result = str(1). endmethod. endclass. class lcl_profiler implementation. method off_len. do n times. lcl_string_util=>get_using_off_len( guid ). lcl_string_util=>get_using_off_len( really_long_string ). enddo. endmethod. method substring. do n times. lcl_string_util=>get_using_substring( guid ). lcl_string_util=>get_using_substring( really_long_string ). enddo. endmethod. method shift. do n times. lcl_string_util=>get_using_shift( guid ). lcl_string_util=>get_using_shift( really_long_string ). enddo. endmethod. endclass.
And the measurements (in microseconds):
Using offset/length or the substring function seems to be largely equivalent, very slightly favoring offset/length. Which makes it the winner!
But the shift approach really is taking over 30x as long. Still, you might say it doesn’t amount to much… it’s a few miliseconds for 10 000 iterations. Who cares?
Let’s try an even longer string by concatenating 50 000 guids. Now the shift method takes 10 seconds, while the first two approaches still only take about 11 miliseconds.
Perhaps if your application crumbles when faced with large volumes of data, you don’t have to do anything crazy to fix it. Maybe you’re just doing a basic thing incorrectly too many times.
Edit: if you’re a new abaper, make sure not to take the suggestions from the comments section too seriously.
- It’s true that the database can do a lot of heavy lifting for you, but code pushdown is not to be used for trivial things.
- Even though you can use sql directly in abap, make sure to encapsulate database access or your code will be difficult to test.
- This comment contains some more measurements of the regex-based solutions. I suggest you stick to the two fast ones above.
And now for the contest part
The problem specification
I need to get the last character of the string, like this…
‘AAAAAAAA BBBB 1 QQQQQQQQ 2’.
i need to take the last letter (2) to do more things b4.
I’m thinking to rotate the string and take the first letter, but i think will exist a FM that do my requirement.
use below logic
str = ‘test’.
data a type i.
a = strlen(str).
Amole’s solution was voted best answer and highlighted in green. I reluctantly copy-pasted the code into a report.
The code does not compile. After fixing a couple of syntax errors, I ran the report to see it print out test, which is not the last character of the word test.
When cleaned up, you can easily see that the code always prints out the original string:
report zfh. data(str) = 'test'. data(len) = strlen( str ). write str(len). " this is equal to write str+0(len). " which is equal to write str.
The author of the question mentions in a comment that they “like a lot the amole solution”. I’m not sure how to feel about that.
Lots of ways to do this. another one would be to use the function module REVERSE_STRING, or STRING_REVERSE. It is something like that. Anyway, from the name, you can guess what it does, then all you need to do is look at the first letter of the string using an offset.
The algorithm proposed by Rich is correct. As for the function module, it’s the second one:
FUNCTION STRING_REVERSE IMPORTING VALUE(STRING) TYPE ANY VALUE(LANG) TYPE ANY EXPORTING VALUE(RSTRING) TYPE ANY EXCEPTIONS TOO_SMALL.
I especially like the lang parameter here, really makes you wonder. So if you are too, this function module will count characters slightly differently in Thai. I choose to forget about this particular fact and hope that strlen does return the universally agreed-upon lengths of characters on unicode systems. I would actually like to know if I’m right about this. Feel free to let me know in the comments.
The solution gets minus points in 2020 where clean code is a thing for relying on untyped parameters and legacy exception handling. Using a static utility method would be a much better choice today.
Also, it doesn’t work if used with the data type string, only on fixed-length character types.
Given the simplicity of the problem we’re trying to solve, this is a gotcha I wouldn’t expect to encounter. The parameter claims it could be anything!
I’m sorry Rich, but I have to disqualify you. But since you still work in SAP 13 years later, it might be fun for you to reminisce on how much changed since then in the comments section.
name = ‘PRABHU’.
n = strlen(name).
n = n – 1. (n = 5)
output = name+n(1) = U.
Due to the unfortunate mixing of explanations and code without actually using comments, Prahbu’s solution does not compile and also loses some points for readability. But I can understand what he means and the algorithm is correct.
Former Member #1
one long way is
first use STRING_LENGTH FM , then subtract 1 from length
then use STRING_SPLIT_AT_POSITION and pass the length previously calculated
Plus point for correct algorithm. #1’s proposed solution would look something like this:
report zfh. data: string type string value 'ABCD', len type i, result type string. call function 'STRING_LENGTH' exporting string = string importing length = len. call function 'STRING_SPLIT_AT_POSITION' exporting string = string pos = len - 1 importing string2 = result.
these function modules also do not work with strings. I’ve already explained my concerns with this as well as usage of function modules.
First get the string lengthof the variable.
data: lv_string type char4 value ‘ABCD’,
lv_new type i,
lv_new1 type char2.
*lv_new = strlen(lv_string).
CALL FUNCTION ‘STRING_LENGTH’
string = lv_string
LENGTH = lv_new
lv_new = lv_new – 2.
lv_new1 = lv_string+lv_new(2).
You get the last two characters.
reward if useful..
Useful, but no reward. Nazeer narrowly misses the final round by misunderstanding the question.
‘AAAAAAAA BBBB 1 QQQQQQQQ 2’.
i need to take the last letter (2) to do more things b4.
He misinterprets the 2 given as the output to the author’s example input, and instead gives the solution on how to return the last 2 characters.
This underlines the importance of taking the time to ask good questions.
Nazeer gets plus points for solving his variant of the problem correctly. His code, once cleaned up, makes sense and even compiles! He also provided an alternative solution.
report zfh. data: lv_string type char4 value 'ABCD', lv_new type i, lv_new1 type char2. *lv_new = strlen( lv_string ). call function 'STRING_LENGTH' exporting string = lv_string importing length = lv_new. lv_new = lv_new - 2. lv_new1 = lv_string+lv_new(2). write lv_new1.
One way to do this is ,
- find the string length by usinf strlen(strVariableName or String) command.
- Find the string lenght-1 posotion of that string.
Would you mind if I just called you Ram? Ram’s algorithm is correct, but he does not specify exactly how to Find the string lenght-1 posotion of that string, which might be important. The ABAP +off(len) syntax might not be familiar to everyone and there might be different methods as well.
Former member #2
use the below code.
data : l_length type i,
l_data = ‘AAAAAAAA BBBB 1 QQQQQQQQ 2’.
l_length = STRLEN( l_data ) . ” here u will get the length of the field l_data.
by using the length read your variable l_data
and get the last 2 records
#2 surprises us with exactly the same misunderstanding of the question Nazeer had. His code is missing the final line of code, which is instead explained, giving us a nice hybrid solution.
from a similar StackOverflow question from 2018:
Note how the code snippets are actually readable without having to copy-paste it somewhere else first. Both solutions produce correct results. Despite its author’s confidence, the second snippet is worse in performance, but it is downvoted and there is a recent comment from Sandra Rossi explaining why it’s slower. Thank you for doing that. Your efforts are not in vain.
And now that I have this out of my system, I can go back to delivering you the intelligent enterprise. Stay tuned.
Adding to the definitive edition, "substring" only works from 702 and up, https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-US/abennews-71-string_processing.htm
I am afraid I have to inform you that the solutions you found are extremely outdated and several paradigm shifts behind. Today you of course do your string processing on the database as it is way faster than the application server and always in memory.
Are you really serious, or is it just there's something I don't understand? And why are you talking of the database? 🙂
If my string was already in memory and I push it down to the database, it becomes even more in memory! I think we're on to something here.
I tried to measure your method as well, but it fails with a syntax error :
strangely, there is no "with" in your query.
I'll just have to out-innovate you:
I guess your database does not use in memory SAP HANA ™ technology and does therefore not support this highly optimized statement! Please consult your nearest SAP consultant on how to upgrade your current system.
If an upgrade is not possible I can also offer a cloud-based solution! With LastCharacterOfAStringAsAService (LCoaSaaS) you can simply call a webservice in your cloud enabled environment and let our experts handle the determination of the last character of a string. Keep your development efforts where they matter, on your product, and don’t waste time on technical details.
I really really hope you’re attempting to be humourous Cloud-based solution indeed! That did make me laugh!
reminds me of https://www.businessinsider.com/npm-left-pad-controversy-explained-2016-3?r=US&IR=T
Yes, I had http://left-pad.io/ in mind
More serious comment: I also had to modify your code to get rid of syntax errors, interestingly enough the method for c36 UUIDs is quite recent (at least not available on 7.40). I also could not figure out how you actually profiled the calls, is this the ADT profiler that you somehow executed on a class?
Edit: Nevermind, got the profiler to work.
This is actually not a class, but a report with two local classes. The order is strange because you have to do the class definitions, then the report body and then the class implementations. I want the examples to be self-contained so you can copy-paste and run them, but still kinda oop.
You can profile a report by using Alt+F9, there must be a button somewhere as well. Unit tests can also be profiled from the test runner, and would also be a good way to go with examples. You can have assertions at the end so the example is executable and verifies itself.
Got it, thanks!
If I recall correctly you should be able to move the implementations above the body if you add an explicit START-OF-SELECTION statement.
Nice tip! Much more readable than having it in the middle.
Well, ABAP CDS always works without problems, and should always be used when possible, so I suggest using https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-us/abencds_f1_sql_functions_character.htm
Oh, and ABAP managed database procedures, but I guess CDS is the default go-to-tech
(Might contain syntax errors, but I’m certainly not going to log into a SAP system on Sunday evening - valid since 702).
Ouch, it hurts... 🙂
It must be almost the same bad performance as:
I wonder that there are no regex solutions...
Here's some more solutions from the comments:
So I guess don't use regexes either 🙂
Here's another one...real quick....short on time....thinking of a "slice" type expression in other languages that takes a string and slices it into an array got me thinking.....for SAP and ABAP....
use standard FM (forget names off hand but there are a few) that takes a string and length (our case would be 1) and then will split the string into an internal table with each entry of the length given and send that back to you. Then, simply get last entry of that internal table.
Wonder how long that one would take. haha
Thanks for sharing! It's helpful that you've included specific information on how different solutions performed.
This could've been a very good blog if only it focused on the solution and had TLDR. The whole "let's put some old answers on trial" part might seem fun but I feel that judging 10+ year old answers this way is rather poor taste. And it distracts from the main point unnecessarily.
If 2018 SCN join date in your profile is correct, you might not know some history behind this. Please allow me to explain.
P.S. Most importantly, why the heck there is still no simple RIGHT( ) function in ABAP? That's the real question here.
I'm sorry if the tone of my post seems too harsh. It's not meant to offend - I poke fun at the old answers the same way I would laugh at my own old code. I think this is because I am a dual person - an engineer and a clown. As such, my blogs also tend to include a mixture of both craftmanship and clownmanship.
I do make an effort to remain understandable to both of my audiences, which is why I often edit my posts to clarify something mentioned in the comments. And I do agree with your point that the engineering part contains most of the important points and the other one distracts from it. As the post is primarily meant to be a thorough answer to the question, I'm sure I can make the text work in an order that reflects this purpose better.
I like that you mentioned a history of scn. Being rather new to the whole thing, I'm actually very interested in how things evolved over time.
While I understand the concept as a whole, I disagree that this particular instance is necromancing, as this is still the first google result for get last character of a string abap today. What prompted me to write this was googling this exact thing and getting a flat-out incorrect answer highlighted in green. I felt the need to correct that. I can get very motivated by mild annoyances.
And you're completely right that this problem wouldn't exist if ABAP offered a more intuitive standard library. This one actually goes deeper - most languages have this built in at syntax level.
Similarly to abap's offset/length notation, in python, you can do
to get a subset of a list, or in abap terms, table.
If you combine this with strings being indexable as tables of characters and allowing negative offset/lengths, you get
This is very flexible and allows you to solve all kinds of similar problems very neatly.
This is very inflexible and means you can't rely on it to solve your problem even if it's solved a similar one in the past. Which makes you google for alternatives and find bad ones.
I'm sure there are reasons for that too. And since we can't change the past, this blog is meant mostly as a call to change the future.
this is a very interesting post. Yesterday I've checked out some possibilities.
It turns out that there exists an builtIn function for to read a substring from a given string. with this help the problem can be solved in a short manner.
It's exactly what Frederik proposed:
Thanks for referencing my post from Stack Overflow! 🙂
I liked your post "How to retrieve the last character".
I wanted to ask you, with what program / transaction did you measure the speed of method execution?
I'm using the profiler which can either be run from Eclipse or the transaction SAT. I think this is currently the best available tool for more complex performance analysis.
If you don't need something complex, the simplest method is retrieving the timestamps before and after the measured block of code, then subtracting them.
Thanks for the quick response. Usually I use SAT, and your screenshot has a slightly different picture :).
I think measuring from eclipse will give you more tracing options, or at least I haven't found them on the sapgui side yet. For example the timeline view:
Very beautiful picture - clearly see, who is stealing time :). I want to try working on eclipse. Frederik, do you use some kind of eclipse plugin for code analysis or is it built into the ABAP for Eclipse extension? Can you share a link to this functionality?
You can run a trace using the profile as... option. Unit tests can also easily be profiled from the test runner.
then you can find the trace in the ABAP Traces view.
I can also open my traces from Eclipse within SAT, so it is probably still SAT under the hood. I just don't have all the visualization options available there.
Frederik thanks for the info.
By the way, I went to your blog through your github-repository, I liked your program Turtle-painter. You have a neat and clear code.
Your comment is not related to the blog post, which is about the LAST CHARACTER of a sentence. Your comment is about the FIRST character of the LAST WORD. The author of the the blog post said that the reverse algorithm is not a good solution because it's very slow. Do you mean you don't agree or what?
Hi, Prabhu's way is working fine for me: