Technical Articles
My Solution to ABAP Community Code Contest `ABAP is excellent `
When Thomas Jung started the ABAP Community Code Challenge on February 28th, I first thought: Okay, this is not a big deal for an advanced programmer. But after the first solutions came up with “made it with 5 lines”, this really challenged me to do this in one line. After some help of my friends Peter Jaeckel and Łukasz Pęgiel I really made it in one line.
Solution
This is my solution:
"Explanation why ABAP has four unique characters and not three as expected:
"the second A is a cyrillic character which looks equal to "normal A" but has different unicode value
PARAMETERS p_text type string DEFAULT `ABАP is excellent ` LOWER CASE.
"class (S)AP (C)ommunity (C)oding (C)hallenge (S)eries
CLASS scccs DEFINITION.
PUBLIC SECTION.
METHODS solve
IMPORTING
text TYPE string
RETURNING
VALUE(result) TYPE string_table.
ENDCLASS.
CLASS test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PUBLIC SECTION.
PRIVATE SECTION.
DATA cut TYPE REF TO scccs.
METHODS setup.
METHODS abap_is_excellent FOR TESTING.
METHODS lorem_ipsum_dolor_sit_amet FOR TESTING.
METHODS many_trailing_spaces FOR TESTING.
METHODS many_leading_spaces FOR TESTING.
METHODS many_space_in_the_middle FOR TESTING.
METHODS complex FOR TESTING.
ENDCLASS.
CLASS scccs IMPLEMENTATION.
METHOD solve.
result = REDUCE string_table( "STRING_TABLE is the output...
LET
"clean the text from spaces
text_wo_double_spaces = replace( regex = '(\s)(?=\1)' val = text with = `` occ = 0 )
"delete trailing and leading blanks
cleaned_text = shift_left( val = shift_right( val = text_wo_double_spaces ) )
"number of words is the number of blanks + 1
num_words = count_any_of( val = cleaned_text sub = ` ` ) + 1 IN
INIT
"fill first result line with "number of words"
lines = VALUE #( ( |Number of words: { num_words }| ) )
" derive words via SEGMENT of cleaned string
FOR word
IN REDUCE string_table(
INIT w = VALUE #( )
"do read the words and store in table (equals SPLIT INTO TABLE)
FOR i = 1 THEN i + 1 UNTIL i > num_words
NEXT w = VALUE #( BASE w ( segment( val = cleaned_text index = i ) ) ) )
NEXT lines = VALUE #(
"transfer word-string to result table (BASE for keeping existing lines)
BASE lines ( |Number of unique characters in the word: { word } - {
"now calculate number of distinct characters in each word
lines( REDUCE string_table(
"prepare the character table
INIT chars = VALUE string_table( )
"do aas many times as the word has characters; pos is the offset for current character
FOR pos = 0 THEN pos + 1 WHILE pos < strlen( word )
NEXT chars = COND #(
"if current character does not exist in character table,...
WHEN NOT line_exists( chars[ table_line = word+pos(1) ] )
"...then insert current character
THEN VALUE #( BASE chars ( word+pos(1) ) )
" otherwise keep current character table as it is
ELSE chars ) ) ) }| ) ) ) .
ENDMETHOD.
ENDCLASS.
CLASS test IMPLEMENTATION.
METHOD setup.
cut = NEW #( ).
ENDMETHOD.
METHOD abap_is_excellent.
cl_abap_unit_assert=>assert_equals(
act = cut->solve( `ABАP is excellent ` )
exp = VALUE string_table(
( `Number of words: 3` )
( `Number of unique characters in the word: ABАP - 4` )
( `Number of unique characters in the word: is - 2` )
( `Number of unique characters in the word: excellent - 6` ) ) ).
ENDMETHOD.
METHOD lorem_ipsum_dolor_sit_amet .
cl_abap_unit_assert=>assert_equals(
act = cut->solve( `Lorem ipsum dolor sit amet` )
exp = VALUE string_table(
( `Number of words: 5` )
( `Number of unique characters in the word: Lorem - 5` )
( `Number of unique characters in the word: ipsum - 5` )
( `Number of unique characters in the word: dolor - 4` )
( `Number of unique characters in the word: sit - 3` )
( `Number of unique characters in the word: amet - 4` )
) ).
ENDMETHOD.
METHOD many_trailing_spaces.
cl_abap_unit_assert=>assert_equals(
act = cut->solve( `start ` )
exp = VALUE string_table(
( `Number of words: 1` )
( `Number of unique characters in the word: start - 4` )
) ).
ENDMETHOD.
METHOD many_leading_spaces.
cl_abap_unit_assert=>assert_equals(
act = cut->solve( ` end` )
exp = VALUE string_table(
( `Number of words: 1` )
( `Number of unique characters in the word: end - 3` )
) ).
ENDMETHOD.
METHOD many_space_in_the_middle.
cl_abap_unit_assert=>assert_equals(
act = cut->solve( `start end` )
exp = VALUE string_table(
( `Number of words: 2` )
( `Number of unique characters in the word: start - 4` )
( `Number of unique characters in the word: end - 3` )
) ).
ENDMETHOD.
METHOD complex.
cl_abap_unit_assert=>assert_equals(
act = cut->solve( ` one two thréé ÄÖÜ ••• ` )
exp = VALUE string_table(
( `Number of words: 5` )
( `Number of unique characters in the word: one - 3` )
( `Number of unique characters in the word: two - 3` )
( `Number of unique characters in the word: thréé - 4` )
( `Number of unique characters in the word: ÄÖÜ - 3` )
( `Number of unique characters in the word: ••• - 1` )
) ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
cl_demo_output=>display_data( NEW scccs( )->solve( p_text ) ).
To make it look more professional, I added some unit tests. 😉
I tried to explain each single statement inline as good as I was able to.
Although I used unit tests, clean code and only one line, I didn’t make it to the finals. 🙁
Maybe next time…
P.S.: Unfortunately I forgot to release this blog post… 🙁
P.S.S.: here is the Current Community Coding Challenge
That also reminds me to complete the Advent Of Code 2019…
Better late than never. I loved the fact that you added so much explanation into the code sample. Great for others to learn from.
Thanks Thomas Jung for your positive feedback!
That also were my thoughts. 🙂
I'm glad to see this one. So yes - nice post, amazing code.
And if I ever had to change anything in it, I would go a little crazy. OK, I'm already crazy. I'd be a lot crazier. 😉
Thanks Michelle Crapo !
That's why this kind of code will never do it to productive environment... 😉
Michelle Crapo think about Enno Wulff , how he needs to be crazy to create such code! 😀
Now that comment made me laugh this morning. Yes, he must be crazy. I think that is a prerequisite all technical people are a bit crazy. Ummm... That means you too. 🙂
Haha, yes I know 🙂 But if we're all crazy, shouldn't that became new normal? 😀
Cool Solution Enno Wulff !!, I also used the count space hack to find total no of words. But I didn’t use regex or reduce and it came in one line ?
I sent almost 5 emails to SAP ? with 5 solutions and in my first solution, I used classes(cl_abap_regex) as I was not aware about the usage of regex in string processing statements at that time. I did that also in a single line ? ? , although it looks ugly.
But I really loved the learning journey from the 1st solution to the final solution. Maybe I will also write a blog post about it soon ?
yeah, Mahesh Kumar Palavalli the learning curve is, what makes fun at least. 😀
If the "ABAP statement hero" badge does not yet exist, I suggest this badge. The first community member that she should get it should be Enno Wulff. Congratulations! 🙂
? well, I also know some others... 😉
oved the way that you included hair guide acnl such a great amount of clarification into the code test. Well done brother.
Thanks helge dopler !
Impressive.
I guess this would be the most extreme interpretation for "one line of code".
haha, yes. And most horrifying... 😀
That's what I was thinking. Write once, read never (well, not that bad!).
Maybe we should have an ABAP Code Obfuscation Challenge https://www.ioccc.org/
Its a wonderful solution and also a demonstration of how the new ABAP syntax can make code utterly impossible to understand, which is why you had to comment most lines...
I really took pains for creating this unreadable “solution”, Paul Hardy 😀
I love and hate the solution at the same time 🙂
Would not like to see this in production but I prefer such style than the old-style code building fieldcatalog for ALV in 2000 lines.
At the end you may refactor this to some additional methods and then no comments are needed.
Hey Łukasz Pęgiel refactoring and splitting into dedicated methods would be the goal to achieve.