Skip to Content
Personal Insights

SAP Community Coding Challenge – March 2020: My Approach

Along with many others I participated in the SAP Community Coding Challenge Series that Thomas Jung recently put on.

It was a neat challenge in that it was simple in nature, but invited different ways to approach the challenge and opportunities to dig into some of the newer, more sophisticated ABAP syntax.

When breaking down the challenge itself I decided right away that a “REDUCE” would help with cheating a on the “lines of code” part of the challenge.

The parts of the challenge that presented more creative opportunities were:

  • determining the number of words in the sentence?
  • iterating to each word in the sentence?
  • counting the unique characters in each word?

 

Pretty straight forward stuff, but how to do it in an efficient way became the challenge.

 

Determining the number of words in the sentence?

 

Some of the older ABAP mechanisms included a “SPLIT” command, but the sentence provided, “ABАP is excellent ” has some extra spaces, so a “CONDENSE” would have been needed. A “SPLIT into table” with a “CONDENSE” inside of it would have returned a table of words and a “lines( )” call would have taken care of the number of words:

SPLIT condense( sentence ) at space into table data(words).

( I’ll show an example solution below that includes this )

But I was looking at a way to basically scan the sentence instead. So I used a condense with regular expressions within a count call to get the number of words needed to display and to support my scan of the sentence:

data(number_of_words) = count( val = condense( sentence ) regex = `(\b[^\s]+\b)` ).

 

Iterating to each word in the sentence?

 

The “SPLIT into table” with an embedded “CONDENSE” would have take care of this, but like I said I wanted to try and scan the sentence instead.

This is where I ran into the “segment” command. Along with the regular expression pattern I was able to do just that:

word = segment( val = sentence index = index space = ` ` )

(you do have to take care in that there is an exception raised if the index is out of bounds)

 

Counting the unique characters in each word?

 

In my opinion this was the more complex part of the challenge and my initial thoughts were to leverage regular expressions again because it would be the most straightforward way to go about it.

Otherwise you could go with some kind of loop or breaking the letters of the word into another table and doing a SELECT DISTINCT kind of thing….

Here’s what I ended up with:

number_of_unique_characters = count( val = word regex = `(\S)(?!.*\1.*)` )

It seemed pretty simple.

 

REDUCE

 

Now that these parts of the process were figured out it was a matter of putting it together inside the “REDUCE” call:

    DATA(sentence) = `ABАP  is excellent `.

    data(number_of_words) = count( val = condense( sentence ) regex = `(\b[^\s]+\b)` ).

    out->write( reduce stringtab(
                init output = value #( ( |Number of words: { conv string( number_of_words ) }| ) )
                     word type string
                     number_of_unique_characters type i
                for index = 1 until index > number_of_words
                next word = segment( val = sentence index = index space = ` ` )
                     number_of_unique_characters = count( val = word regex = `(\S)(?!.*\1.*)` )
                     output = value #( BASE output ( |Number of unique characters in the word: { word } - { conv string( number_of_unique_characters ) }| ) ) ) ).

I used a stringtab to make the writing of the results more convenient as well as string templates.

This particular implementation ended up being one of my submissions.

The other was the one I was awarded the “Test Class Award” for.

 

Alternative using SPLIT into table

 

Here is how I might go about it had I decided to split the words into a table:

    DATA(sentence) = `ABАP  is excellent `.

    SPLIT condense( sentence ) at space into table data(words).

    out->write( reduce stringtab(
                init output = value #( ( |Number of words: { conv string( lines( words ) ) }| ) )
                     number_of_unique_characters type i
                for index = 1 until index > lines( words )
                next number_of_unique_characters = count( val = words[ index ] regex = `(\S)(?!.*\1.*)` )
                     output = value #( BASE output ( |Number of unique characters in the word: { words[ index ] } - { conv string( number_of_unique_characters ) }| ) ) ) ).

Just as good if not a better implementation………I should have submitted this one too 🙂

 

Final Thoughts

 

As some people have noted regular expressions can get pretty rough.  They’ve come in very handy for me in various projects.

I personally use tools such as Regex Magic and Regex Buddy to help with building the expressions and testing them out.

I personally would like to see more challenges like this, similar to something like Code Signal (formerly Code Fights), where you get a set of unit tests to pass for each challenge, and once successfully completed you can see how other people approached the same challenge and even vote for your favorite.

But I’m fine with Thomas Jung and Rich parsing through hundreds of submissions too..Lol.

Huge shout out to them for facilitating this first challenge and hopefully more to come!

Cheers,

James

9 Comments
You must be Logged on to comment or reply to a post.
  • Hello James,

    thanks for documenting your approach and your code with unit tests.

    From the 7 solutions up to vote,

    • #1,#2,#3,#4 and #7 used SPLIT and condense/delete to extract words from the sentence
    • #5 used the regex pattern `\w+` and
    • #6 used segment.

    To count the unique chars

    • #2 and #5 used replace to remove duplicates found using the regex pattern `(.)(?=.*\1)`
    • #1 used cl_abap_matcher->find_all( ) with regex pattern ‘(.)(?!.*\1)’
    • #7 used the Contains Any (CA) operator in a REDUCE expression

    The others converted the words in a char table and then used

    • #1 SELECT DISTINCT
    • #3 GROUP BY or
    • #6 CORRESPONDING DISCARDING DUPLICATES

    I like #2 but to me, a regex is not as expressive as a built-in statement like #1, #3 or #6. My vote went to the GROUP BY solution. I prefer the form:

    SPLIT condense( sentence ) AT space INTO TABLE DATA(words).
    out->write( |Number of words:{  lines( words ) }| ).
        
    LOOP AT words INTO DATA(word).
      DATA(characters) = VALUE string_table( FOR offset = 0 UNTIL offset = numofchar( word ) ( word+offset(1)  ) ).
      DATA(unique_characters) = VALUE string_table( FOR GROUPS c OF char IN characters GROUP BY char ( c ) ). 
      out->write( |Number of unique characters in the word:{ word } - { lines( unique_characters ) }| ).
    ENDLOOP.

    Disclaimer: I wrote #6.

    JNN

    • Hey Jacques,

      I ended up voting for #2 as well.  Very neat to see the different approaches.

      And the segment syntax was new to me, was it something you knew about before?

      Also agree that regex is not easy to read as it is.

      Cheers,

      James

  • Hi James, I tried to make REDUCE even more readable with my solution.

    How do you like it?

    split condense( sentence ) at space into table data(words).
    out->write( reduce #( init text = |Number of words: { lines( words ) }\n|
                           for word in words
                          next text = |{ text }Number of unique characters in the word: { word } - | &&
                                      |{ strlen( replace( val = word regex = '(.)(?=.*\1)' occ = 0 with = '' ) ) }\n| ) ).

     

    BR, Grzegorz

  • Hi James.

    Thank you for sharing…

    I’d like ask you which “Application/Language” did you choose in Regex Magic and Regex Buddy when your target was to use it in ABAP.

    Thank you in advance for your answer James