Application Development and Automation Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

SAP Community Coding Challenge - March 2020

thomas_jung
Developer Advocate
Developer Advocate
7,995

This is the voting thread for the March 2020 SAP Community Coding Challenge. For the challenge details and directions see this blog:

https://blogs.sap.com/2020/02/28/sap-community-coding-challenge-series/

In this Question thread I will post the 7 finalist. Use the answer voting mechanism to choose the one solution you believe should be the overall winner. Remember: this is all for fun and education. We are all winners here because of the great knowledge sharing!

53 REPLIES 53

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #1: CL_ABAP_MATCHER Approach

By pawelgrzeskowiak

    SPLIT condense( sentence ) AT space INTO TABLE DATA(words).


    out->write( |Number of words: { lines( words )  } | ).


    LOOP AT words ASSIGNING FIELD-SYMBOL(<word>).
      DATA(matcher) = NEW cl_abap_matcher(
                      regex = NEW cl_abap_regex( pattern = '(.)(?!.*\1)' )
                      text  =  <word> ).
      out->write( |Number of unique characters in the word: { <word> } - {  lines( matcher->find_all( ) )  } | ).
    ENDLOOP.

3,131

I see that there are two times more people who prefer Finalist #1 over Finalist #2. Their solutions are almost identical except the way they invoke the regular expression and get the results:

  1. Finalist #1: cl_abap_reger + cl_abap_matcher + find_all + lines
  2. Finalist #2: replace regex + strlen

I'm interested to hear from people the reasons why they prefer Finalist #1 over Finalist #2...

0 Kudos
3,131

I would say Finalist #1 code looks more clean, in terms of readability, compared to Finalist #2.

0 Kudos
3,131

I would go with this one as the easiest to read.

And it also follows the basis of clean code convention.

0 Kudos
3,131

Nice clean solution! +1

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #2: Regex Approach

By: alexander_frank

    SPLIT condense( sentence ) AT space INTO TABLE DATA(words).
    out->write( |Number of words: { lines( words ) }| ).
    LOOP AT words REFERENCE INTO DATA(word).
      DATA(unique_chars_in_word) = replace( val = word->* regex = `(.)(?=.*\1)` with = `` occ = 0 ).
      out->write( |Number of unique characters in the word: { word->* } - { strlen( unique_chars_in_word ) }| ).
    ENDLOOP.

3,131

Although the regex looks weird and would be difficult to maintain by "standard" developers if there's an error or change request, the code is extremely clear. Bravo!

3,131

I like it, very clean code! But clear, the regex some time can be a pain to understand and if you don't have some nice variable naming you can end up scratching your head trying to understand what happens.

3,131

Agreed. Simple and clean.

I liked the other finalists as well. Neat to see all the difference approaches.

3,131

II like this solution. Well besides the variable name

3,131

Very clean and straight forwars. I like it.

AlexFrank
Product and Topic Expert
Product and Topic Expert
3,131

Wow, this was more praise than I've gotten since a long time for my code. Perhaps I should also compliment other code more often.

My thought process was quite short, so I don't think it warrants a full blog post.

I tried to find the solution with the least lines of code that mostly complies with the SAP ABAP Style Guide. Since I've done Advent of Code and other coding challenges in ABAP, I strongly expected to use regex quite a lot if I wanted a short solution. Therefore this was my first implementation and I couldn't think about another solution afterwards that could possibly be tuned to less lines of code. Using a lot of functions like lines( ) or strlen( ) within string templates was needed in order to keep the lines of code low. Other choices like using REFERENCE INTO or putting the result of the replace( ) function with the regex into a distinct variable were done due to the Style Guide or Clean Code in general. I didn't assemble the regex with constants as it is proposed in the Style Guide since I thought that it wasn't too complex. But I'm sure this is debatable.

With what I know now, I would probably use count( ) instead of replace( ) since I then don't need the strlen( ) call and perhaps tweak a solution with REDUCE instead of LOOP AT. This blog post and its comments has some nice ideas: https://blogs.sap.com/2020/03/18/sap-community-coding-challenge-march-2020-my-approach/

0 Kudos
3,131

Well done but constructing the correct Regex is another story...

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #3: For/Group By Approach

By: sougata.chatterjee

    SPLIT condense( sentence ) AT space INTO TABLE DATA(lt_words).
    out->write( |Number of Words: { lines( lt_words ) }| ).

    LOOP AT lt_words ASSIGNING FIELD-SYMBOL(<word>).
      out->write(
    |Number of unique characters in the word: { <word> } - {
       lines( VALUE string_table(
        FOR GROUPS x OF y
        IN VALUE string_table(
            FOR i = 0 THEN i + 1
            UNTIL i = strlen( <word> ) ( <word>+i(1) ) )
            GROUP BY y ( x ) ) ) }| ).
    ENDLOOP.

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #4: SELECT DISTINCT Approach

By: dominik.bigl2

    SPLIT condense( sentence ) AT | | INTO TABLE DATA(words).
    out->write( |Number of words: { lines( words ) } | ).

    LOOP AT words ASSIGNING FIELD-SYMBOL(<word>).
      DATA(characters) = VALUE ABAP_SORTORDER_tab( FOR char = 0 THEN char + 1 UNTIL char = strlen( <word> ) ( name = <word>+char(1) ) ).
      SELECT DISTINCT * FROM @characters AS characters INTO TABLE @DATA(unique_characters).
      out->write( |Number of unique characters in the word: { <word> } - { lines( unique_characters ) } | ).
    ENDLOOP.

3,131

absolutly love it !

3,131

perfect solution!

3,131

FROM @ works only with HANA

0 Kudos
3,131

love this solution!

0 Kudos
3,131

perfect solution

3,131

I mean, in Open SQL, SELECT ... FROM @ is only supported if the database is HANA (*). For instance, SAP Sybase ASE (16.0) doesn't support it (Developer Edition 7.52). Of course, it works with the ABAP Cloud environment, but generally speaking, many SAP clients don't have HANA (yet).

(*) EDIT:

SELECT DISTINCT works only if the database is HANA.

Complete explanation: FROM @ITAB works from 7.52 only if

  • either the SQL statement is really simple (no join, no order by, no distinct, etc.) so it's executed by ABAP kernel,
  • or it's not really simple and it's executed by the database and only HANA currently supports it.

0 Kudos
3,131

really a great approach.

congrats!

HugoJ
Product and Topic Expert
Product and Topic Expert
3,131

Amazing, super clean, concise, readable and maintanable 😛 What more can you ask?

0 Kudos
3,131

Beautiful than i wrote lol ! I should learn from them...Good job!

BiberM
Active Participant
0 Kudos
3,131

Very clever solution! Nothing I would implement in my code base (data transfer overhead for limited benefit) but extra kudo for creativity!

0 Kudos
3,131

Selection from the internal table limits the application of this solution. Easier DELETE ADJACENT DUPLICATE ENTRIES FROM characters.

3,131

Fun solution but EXTREMELY slow, 700 times slower than Finalist #2 ! I thought the solution had to be realistic but in fact the probable winner will be the craziest. ¯\_(ツ)_/¯

CLASS ltc_main DEFINITION
      FOR TESTING
      DURATION SHORT
      RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    DATA sentence TYPE string VALUE `ABАP  is excellent `.
    METHODS select_from_itab FOR TESTING.
    METHODS regex FOR TESTING.
ENDCLASS.
CLASS ltc_main IMPLEMENTATION.
  METHOD regex.
    DO 3000 TIMES.
      SPLIT condense( sentence ) AT space INTO TABLE DATA(words).
      DATA(a) = |Number of words: { lines( words ) }|.
      LOOP AT words REFERENCE INTO DATA(word).
        DATA(unique_chars_in_word) = replace( val = word->* regex = `(.)(?=.*\1)` with = `` occ = 0 ).
        DATA(b) = |Number of unique characters in the word: { word->* } - { strlen( unique_chars_in_word ) }|.
      ENDLOOP.
    ENDDO.
  ENDMETHOD.
  METHOD select_from_itab.
    DO 3000 TIMES.
      SPLIT condense( sentence ) AT | | INTO TABLE DATA(words).
      DATA(a) = |Number of words: { lines( words ) } |.
      LOOP AT words ASSIGNING FIELD-SYMBOL(<word>).
        DATA(characters) = VALUE abap_sortorder_tab( FOR char = 0 THEN char + 1 UNTIL char = strlen( <word> ) ( name = <word>+char(1) ) ).
        SELECT DISTINCT * FROM @characters AS characters INTO TABLE @DATA(unique_characters).
        DATA(b) = |Number of unique characters in the word: { <word> } - { lines( unique_characters ) } |.
      ENDLOOP.
    ENDDO.
  ENDMETHOD.
ENDCLASS.

3,131

Unfortunately, I didn't have this brilliant idea. I am a big fan of clean code. In terms of readability or maintainability, this is the cleanest solution in my opinion. Thank you Domi!

3,131

You should win ... if you don't you still should be given a gold medal for creativity! Well done!!!

0 Kudos
3,131

some learning this _/|\_

0 Kudos
3,131

I have been using this approach for the last 6 months and it works as a charm. i would have personally gone with this if i participated. Clear and pure ABAP.

0 Kudos
3,131

Totally impressed with this solution.

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #5: Single Line Solution

By: christian.guenter

 out->write( CONV string(
                          LET sentence = `ABАP  is excellent `
                              match_word_regex = `\w+`
                              match_duplicate_chars_regex = `(.)(?=.*\1)`
                              num_of_words = count( val   = sentence
                                                    regex = match_word_regex )
                          IN REDUCE #( INIT output = |Number of words: { num_of_words }|
                                       FOR word_index = 1 WHILE word_index <= num_of_words
                                       LET word = match( val   = sentence
                                                         regex = match_word_regex
                                                         occ   = word_index )
                                           num_of_unique_chars = numofchar( replace( val   = word
                                                                                     regex = match_duplicate_chars_regex
                                                                                     occ   = 0
                                                                                     with  = space ) )
                                       IN NEXT output = output && |\nNumber of unique characters in word: { word } - { num_of_unique_chars }| ) ) ).


0 Kudos
3,131

Wow! Well done mate.

0 Kudos
3,131

But then again,

The Regex makes it easier for constructing the Expression; finding the correct Regex is another story though...

match_word_regex =`\w+`
match_duplicate_chars_regex =`(.)(?=.*\1)`

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #6: Sorted Table Discarding Duplicates Approach

By: jacques.nomssi

    TYPES sorted_char_table TYPE SORTED TABLE OF string WITH UNIQUE DEFAULT KEY.
    DO.
      TRY.
          DATA(word) = segment( val = sentence index = sy-index space = ` ` ).
          DATA(characters) = VALUE string_table( FOR idx = 0 UNTIL idx = numofchar( word )  ( word+idx(1)  ) ).
          DATA(chars_in_word) = CORRESPONDING sorted_char_table( characters DISCARDING DUPLICATES ).
         out->write( |Number of unique characters in the word:{ word } - { lines( chars_in_word ) }| ).

        CATCH cx_sy_strg_par_val.
          out->write( |Number of words:{  sy-index - 1 }| ).
          EXIT.
      ENDTRY.
    ENDDO.

0 Kudos
3,131

Hi, I didn't know the DISCARDING DUPLICATES addition. Great, thx

thomas_jung
Developer Advocate
Developer Advocate
3,131

Finalist #7: Reduce Approach

By: dinumbabu

    SPLIT sentence AT ` ` INTO TABLE DATA(words).
    DELETE words WHERE table_line IS INITIAL.
    out->write( |no of words: { lines( words ) } | ).

    LOOP AT words ASSIGNING FIELD-SYMBOL(<word>).
      out->write( |Number of unique characters in the word: { <word> } - {
                                  REDUCE i( INIT len = 1
                                            FOR n = 1  WHILE ( n <= strlen( <word> ) - 1 )
                                            NEXT len =  len + COND #( WHEN <word>+0(n) CA <word>+n(1) THEN 0 ELSE 1  ) ) } | ).
    ENDLOOP.