Skip to Content
Technical Articles

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:

  • offset/length
  • substring
  • 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.

The contenders

 

Amole

Hi,

use below logic

str = ‘test’.

data a type i.

a = strlen(str).

write str(a).

Regards

Amole

Verdict

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.


Rich Heilman

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.

Verdict

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.


Prabhu

name = ‘PRABHU’.

n = strlen(name).

n = n – 1. (n = 5)

output = name+n(1) = U.

Regards

Prabhu

Verdict

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

Verdict

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.

Unfortunately,

these function modules also do not work with strings. I’ve already explained my concerns with this as well as usage of function modules.


Nazeer

Hi,

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’

EXPORTING

string = lv_string

IMPORTING

LENGTH = lv_new

 

lv_new = lv_new – 2.

lv_new1 = lv_string+lv_new(2).

write lv_new1.

You get the last two characters.

reward if useful..

regards,

nazeer

Verdict

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.

Ramgansean K

Miguel,

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.

Regards,

<b>Ramganesan K</b>

Verdict

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 importantThe 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(50).

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

Verdict

#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.


Additional entries

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.

30 Comments
You must be Logged on to comment or reply to a post.
  • 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.

      METHOD get_using_in_memory_blockch_ml.
        EXEC SQL.
          SELECT right( :str, 1 ) INTO :result FROM dummy;
        ENDEXEC.
      ENDMETHOD.
    • 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:

      • Make a bot which will tweet the question |Hey guys, what is the last letter of { str }?|.
      • Wait one week for responses (waiting for a fixed period of time is O(1), so really fast).
      • Use NLP to process the responses, making use of AI and machine learning.
      • Decide based on a majority vote, giving power back to the people. You’ll be exposing yourself to a 51% attack, but it works for blockchain and it’s not like people would just lie on twitter anyway.
      • I tried to measure your method as well, but it fails with a syntax error

        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.

      • 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.

  • METHODS get_last_char
      IMPORTING
        i_string_to_find_last_char_of TYPE csequence
      RETURNING
        value(r_result) TYPE string.
    
    METHOD get_last_char.
      r_result = substring( val = reverse( i_string_to_find_last_char_of ) len = 1 ).
    ENDMETHOD.

    (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:

      METHOD get_last_char.
        r_result = match( val = i_string_to_find_last_char_of regex = '.' occ = -1 ).
      ENDMETHOD.
    • Here’s some more solutions from the comments:

      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.
      
          class-methods get_using_builtin_reverse
            importing str           type string
            returning value(result) type t_last_character.
      
          class-methods get_using_find_regex
            importing str           type string
            returning value(result) type t_last_character.
      
          class-methods get_using_regex
            importing str           type string
            returning value(result) type t_last_character.
      
      
      endclass.
      
      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.
      
        method get_using_builtin_reverse.
          result = substring(
            val = reverse( str )
            len = 1 ).
        endmethod.
      
        method get_using_find_regex.
          find regex '.$' in str results data(res).
          result = str+res-offset(res-length).
        endmethod.
      
        method get_using_regex.
          result = match( val = str regex = '.' occ = -1 ).
        endmethod.
      
      
      endclass.
      
      
      class lcl_profiler definition create private.
        public section.
      
          class-data: guid type string.
          class-data: really_long_string type string.
          class-data: n type i.
      
          class-methods:
            off_len,
            substring,
            shift,
            builtin_reverse,
            find_regex,
            regex.
      
      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.
      
        method builtin_reverse.
          do n times.
            lcl_string_util=>get_using_builtin_reverse( guid ).
            lcl_string_util=>get_using_builtin_reverse( really_long_string ).
          enddo.
        endmethod.
      
        method find_regex.
          do n times.
            lcl_string_util=>get_using_find_regex( guid ).
            lcl_string_util=>get_using_find_regex( really_long_string ).
          enddo.
        endmethod.
      
        method regex.
          do n times.
            lcl_string_util=>get_using_regex( guid ).
            lcl_string_util=>get_using_regex( really_long_string ).
          enddo.
        endmethod.
      
      
      endclass.
      
      
      start-of-selection.
      
        lcl_profiler=>n = 10000.
      
        lcl_profiler=>guid = `CAC01893-9097-1EEA-8F89-E062D3164D49`.
        lcl_profiler=>really_long_string = ``.
      
        do 5000 times.
          lcl_profiler=>really_long_string = lcl_profiler=>really_long_string && lcl_profiler=>guid.
        enddo.
      
        lcl_profiler=>off_len( ).
        lcl_profiler=>substring( ).
        lcl_profiler=>shift( ).
        lcl_profiler=>builtin_reverse( ).
        lcl_profiler=>find_regex( ).
        lcl_profiler=>regex( ).

       

      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.

    1. SCN is 16 years old. In human terms, 2007 SCN (SDN at that time) was a toddler. There was not as much content and not enough people to correct every wrong answer. There was also no separation of answers/comments and no voting on the answers. (This functionality was introduced only in 2016.) Moderation on SCN really stepped up around 2008-2009, I believe, so there is much less nonsense dated post 2008.
    2. There were 2 SCN platform (and name) changes since 2007. The option to format code in a reply might not have existed back then or even if it did, a lot of formatting either did not survive 2 migrations or was badly affected by them. (E.g. I’m finding many old answers that are now missing links completely.)
    3. As a result of the last migration in 2016, all the old Q&As were moved into archive. They were displayed differently than current Q&A and it was not possible to reply to them anymore. But for some reason later the archive came back to life. This caused quite a bit of annoyance (Exhibit A) and, personally, I feel it was a very bad decision. Perhaps if the 2007 question remained in the archive it would not have prompted this type of reaction.
    4. The concerns about low-quality old content are valid and have been raised many times before. Pursuing an “SCN cleansing” would require resources that it never had (and pre-2016 gamification also worked against that). Even though the call to correct the old answers is well intended, I would advise against doing that for such old answers. This would be necromancing and it’d cause more confusion and annoyance (see also Exhibit A above) than it’s worth. If someone observes a large number of unanswered or poorly answered questions on the same subject then posting a blog is a more effective solution.

    P.S. Most importantly, why the heck there is still no simple RIGHT( ) function in ABAP? That’s the real question here.

    Thank you.

     

    • 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

      slice = list[idx1:idx2] 

      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

      last = str[-1:]

      This is very flexible and allows you to solve all kinds of similar problems very neatly.

      but abap

      • does not allow negative indices
      • does not allow indexing/looping over strings
      • does not allow expressions in arbitrary positions, so you can’t do a one-liner like str+(strlen(str)-1)(1).

      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.

  • Hi all,

    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.

     DATA(lv_test) = |MyStringToInvestigate|.
     DATA(lv_char) = substring( val = lv_test 
                                off = strlen( lv_test ) - 1 
                                len = 1  ).
    

    Greetings

    Matthias

  • HI Frederick,

    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?

    Thanks.
    best regards

    • Hi Andrey,

      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.

        • 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?

            Thanks.

          • 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.

            Good luck!