Skip to Content

Some time ago while browsing the community site, I came across a question where someone asked to output butterfly pattern in ABAP. Normally such problems/questions are common in interviews of other programming languages such as C++ or Java where participants are asked to develop a short program in order to solve a given problem such as generate or perform some operation on a Fibonacci sequence, sort some array or list with some unique conditions or implement binary search etc.

But ABAP!!! I was happy to know that now ABAPers are treated as normal programmers or at least the world has started asking those questions that other programmers are answering for a long time.

The reason for this blog was not to present a solution but rather explain the way this solution was achieved. The solution presented is how I understood and solved the problem. I am sure there will be many other ways to solve this. You are welcome to share your solutions.

The Problem

For a given number (for example 3) out put a pattern that should be as following

Figure 1

As you can see the output matches a butterfly wings (in a symbolic way), hence the name Butterfly pattern.

Solution

Since we are talking about a generic solution for any given number n, it is better to have some more instances of output for different numbers and try to find the pattern there. So let’s have a pattern for 4 and 5 there.

Figure 2

Figure 3

What we can observe is that output is a square matrix that is number of rows and columns in output are equal. That’s first observation.

Then we try to establish a link between the input n and the number of rows/columns in output.So we observe that for input 3, we have 5 rows/columns in output, for input 4 it is 7 and for input 5 it is 9 rows/columns in output.

That solves the rows/columns issue. We have seen from these examples that for any given input n, the number of rows/columns will be 2n-1.

n 2n-1
3 5
4 7
5 9

OK, so this concludes we can have two loops (from 1 till 2n-1) where outer one can be for columns and inner one for rows OR vice versa depending on the logic we choose. In this solution, I have chosen to fill columns as this seems easier. If you look at fig 1,2,3 you will notice that values in a column are either space or some number. And if it is a number, it is same for the whole column.

Thus we can have outer loop for columns and inner for rows where we try to fill a whole

For col = 1 till (2n-1)
 For row = 1 till (2n-1)
  Fill cell(row, col)  “Fill either with space or a number
 End for row
End for col

Now we have to find out for a given column, what is the logic or formula to print the value and how can we deal with space or blank values.

First the number or value printed in each column.

Referring to fig 1,2,3 we see that first column is always filled with number n , the next one with n-1, next one with n-2 and this goes on until the value reaches 1. Once it reaches 1, it starts incrementing. So the column right afterwards will have a value of 2, the next one will have 3 and next one ……….  You get the idea.

The pattern here we can observe is that column value starts from n, it keeps on decreasing until it reaches 1 and then it keep on increasing until it reaches n again.  So if we for example look at figure 3 (butterfly pattern for 5), we can the following pattern (just refer to first two columns of table below)

Column Value (in column) n – Column ABS(n – Column) + 1  
1 5 5-1 = 4 5
2 4 5-2 = 3 4
3 3 5-3 = 2 3
4 2 5-4 = 1 2
5 1 5-5 = 0 1
6 2 5-6 = -1 2
7 3 5-7 = -2 3
8 4 5-8 = -3 4
9 5 5-9 = -4 5

Can we develop some logic here? Looking at the table above, we can see that the difference between column number and value is constant. In this table observe third column. Here we are subtracting column number from the given input n. See the result value fluctuates from 4 till -4. Looking at first five rows of above table we can assume a formula here like

PrintValue = (n – Column) + 1

However this will fail for sixth row as in this case n- column will result in 5-6 = -1. What if we use “Absolute” value function? That can solve the problem. So the above formula can be re-written as

PrintValue = ABS(n – Column) + 1   “ABS will return absolute value for the given argument

This solves the issue of what value to be printed in each column.

So our pseudo code will become something like

For col = 1 till (2n-1)
 For row = 1 till (2n-1)
  Value in cell(row, col)  = ABS(n – col) + 1
 End for row
End for col

Now we address the issue of blanks. How to deal with blank values? Again if we look at fig 3, we can see a pattern of blanks here. Since we are trying to fill the columns first in this solution, so let’s analyze the columns and see if there is a pattern for blank spaces. For Column 1, there are no blanks. For column 2 we have two blank values; one at first row and one at last row. Similarly for column 3 we have total 4 blanks with two at top two rows and two at bottom two rows.

Column Blanks Top Blanks Bottom n – PrintValue
1 0 0 5-5 = 0
2 1 1 5-4 = 1
3 2 2 5-3 = 2
4 3 3 5-2 = 3
5 4 4 5-1 = 4
6 3 3 5-2 = 3
7 2 2 5-3 = 2
8 1 1 5-4 = 1
9 0 0 5-5 = 0

With ref to table above, if we ignore the bottom rows for the time being, we can see a pattern. The sequence of blanks goes from 0 till 4 and then back to 0. This pattern is very much like the pattern we see in “Value” part above where we make use of ABS function to get absolute value. Can we reuse that “Value” here?  Seem like we can. Refer to the fourth column in above table. This formula returns the number of blanks (top or bottom) for any given column. Once we have this value; let’s call it pad value, we can use a simple if statement to check if the current row should print blank or “PrintValue”

If statement could be something like

If current row <= “Pad value”
then print blank
else print Value

The above snippet can cover the top part. For bottom part we need to add something like

Bottom row = ( 2n – 1) – current row
If bottom row <= “Pad value”
then print blank
else print Value

 

That concludes pretty much the whole logic. The ABAP program given below uses an internal table with structure referring to type below. It has two fields to represent row and column. The third field represents the value at that row/column.

TYPES: BEGIN OF tt_tab,
row TYPE i,
col TYPE i,
val TYPE i,
END OF tt_tab.

Program class has two Methods and a Constructor. The constructor just initializes the private variable lv_inp with input parameter p_inp. The first method “Create_pattern” fill the internal table mentioned above with relevant values for butterfly pattern.

The second method “output_pattern” is a quick and dirty way to output the pattern. Obviously more formatted output can be obtained by using different output options such as ALVs, web Dynpros or BSPs (HTML table).

*&---------------------------------------------------------------------*
*& Report  YYRJ_TEST0079
*&
*&---------------------------------------------------------------------*
*& Create a butterfly pattern against a given input p_inp.
*&  Due to restrictions on list width, please do not enter
*&  a value bigger than 38 in p_inp.
*&---------------------------------------------------------------------*
REPORT yyrj_test0079 LINE-SIZE 1023.

PARAMETERS: p_inp TYPE i OBLIGATORY. "input number

CLASS butterfly_pattern DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF tt_tab,
             row TYPE i,
             col TYPE i,
             val TYPE i,
           END OF tt_tab.
    METHODS:
      constructor
        IMPORTING p_input TYPE i,
      create_pattern,
      print_pattern.
  PRIVATE SECTION.
    DATA: lv_inp TYPE i.
    DATA: lt_tab TYPE STANDARD TABLE OF tt_tab,
          ls_tab TYPE tt_tab.
ENDCLASS.

CLASS butterfly_pattern IMPLEMENTATION.
  METHOD constructor.
    lv_inp = p_inp.
  ENDMETHOD.  "constructor
  METHOD create_pattern.

    DATA: lv_n   TYPE i,  "total num of rows or cols in pattern
          lv_row TYPE i,  "row counter
          lv_col TYPE i,  "column counter
          lv_val TYPE i,  "value to be printed
          lv_pad TYPE i.  "pad counter for each column

    lv_n =  ( lv_inp * 2 ) - 1.
    lv_val = lv_inp.
    lv_pad = lv_inp.
    DO lv_n TIMES.
      lv_col = sy-index.
      lv_val = abs( p_inp - lv_col ) + 1.
      lv_pad = p_inp - lv_val.
      DO lv_n TIMES.
        lv_row = sy-index.
        CLEAR ls_tab.
        ls_tab-row = lv_row.
        ls_tab-col = lv_col.
        IF ( sy-index <= lv_pad ) OR ( ( lv_n - lv_row ) < lv_pad ).
          CLEAR ls_tab-val.
        ELSE.
          ls_tab-val = lv_val.
        ENDIF.  "lv_pad check
        APPEND ls_tab TO lt_tab.
      ENDDO.  "lv_n for row
    ENDDO. "lv_n for col
  ENDMETHOD.  "create_pattern
  METHOD print_pattern.
    DATA: lv_n   TYPE i,  "total num of rows or cols in pattern
          lv_row TYPE i,  "row counter
          lv_col TYPE i.  "column counter

    lv_n =  ( lv_inp * 2 ) - 1.
    DO lv_n TIMES.
      lv_row = sy-index.
      DO lv_n TIMES.
        lv_col = sy-index.
        READ TABLE lt_tab INTO ls_tab WITH KEY row = lv_row
                                               col = lv_col.
        IF sy-subrc = 0.
          IF ls_tab-val IS INITIAL.
            WRITE: ls_tab-val.
          ELSE.
            WRITE: ls_tab-val COLOR 3.
          ENDIF. "ls_tab-val
        ENDIF.  "subrc read table
      ENDDO.  "lv_n for lv_col loop
      SKIP 1.
    ENDDO.   "lv_n for lv_row loop
  ENDMETHOD. "print_pattern
ENDCLASS.

DATA: lo_patt TYPE REF TO butterfly_pattern.

START-OF-SELECTION.
  CREATE OBJECT lo_patt EXPORTING p_input = p_inp.
***  create pattern
  lo_patt->create_pattern( ).
*** output pattern
  lo_patt->print_pattern( ).

Sample output for n = 4

Sample output for n = 9

To report this post you need to login first.

24 Comments

You must be Logged on to comment or reply to a post.

  1. Florian Henninger

    Nice blog with a nice solution. Hope to find my time to think about an own solution to it.

    One thing I have to add.

    I never felt like a “special” developer. This is just what you do about it. With ABAP you can solve every problem you want to.. that is exact the same which all other languages do out there.

    Solving problems 😉

    (4) 
    1. Rashid Javed Post author

      I was talking about perception. Traditionally ABAPers were perceived as a business analysts who has learned “some” coding. Talking about 90s and early 2000. Thanks to SCN, now i do see people manipulating bitmaps, creating ray tracers and interfacing with hardware in ABAP. Plus SAP has added a lot of features to ABAP over these years.

      Of course in the end it is all about solving problems!

      (1) 
  2. Sergey Shablykin

    Hi, Rashid! Have you ever heard about practical use of the butterfly pattern in business applications or it’s merely just problem for candidate’s intervew?

    (2) 
    1. Matthew Billingham

      It’s a common homework question on programming courses.

      Just as a matter of interest, I regularly see the question “how do I write a program in ABAP to display a butterfly pattern” posted by trainees – and reject it, on the grounds that someone doing your homework for you means you will never learn. 

      My fear is that such folk will find this blog and just copy paste the answer. My hope is that their instructors are more intelligent than them, and will fail them for plagiarism/cheating. 

      (6) 
      1. Rashid Javed Post author

        Well, wouldn’t it be unfair!  I mean people getting such homework assignments in Java or C++ can google and find numerous examples, why not ABAP folks 🙂

        Also if the purpose is to discourage copy/pasting, may be teachers can be more creative with assignment like print only right side of butterfly pattern or print butterfly pattern with just one characters like * . ……

        (0) 
        1. Matthew Billingham

          You’re right. It’s unfair to C++ and Java programmers if their ABAP colleagues have to work it out for themselves, and in the process gain valuable learning experiences and become better programmers (and indeed better human beings).

          😀

          (3) 
    1. Rashid Javed Post author

      Sorry this make you sad. But in my defense I was assuming that only following version of perform was obsolete

      PERFORM subr(prog) [IF FOUND]

      Never knew that the statement itself is obsolete. 🙂

      (0) 
  3. Jelena Perfiljeva

    Not to rain on anyone’s parade and personally, I’m just not into this kind of academic exercise. (Didn’t finish Human Resource Machine for the same reason – like what’s the business case? 🙂 )

    And I have no beef with PERFORM or Hungarian notation (surprised no one complained about it so far, I guess pitchforks are weighing them down 😉 ). But the comment “do not enter value larger than 38” and no actual check of the parameter’s value – that’s where things can really go wrong in the real life.

    Good job on the blog though.

    (2) 
    1. Rashid Javed Post author

      Thanks for your valuable input. Error handling or value checks were not done because it was more about explaining the algorithm, that’s why code not directly relevant to calculating pattern was removed.

      Also you can provide input greater than 38. Pattern calculation is not the issue. But for large value, the output formatting got all mixed up.

       

      (0) 
  4. Gerrit Beukema

    I like the exercise of using ABAP to solve little programming exercises that the language is not designed or known for. Also, great way to explain how you came to your solution, that really is the more important part of the answer to any of these kinds of questions.

    However, if I were to give this as an interview question and you would come with that solution, I would probably not hire you. First off, your solution does not solve the exercise. Using zeroes instead of blanks is the first deviation. Second is the addition of color. If you cannot follow simple and clear requirements, I would not want you on my team.

    Another problem I have with your solution is the complexity. ABAP was created initially to generate reports and all you are really doing here is writing a report. There are no complicated calculations, you are just outputting some numbers. Why use a class? Why fill an internal table? And not optimize it for the reads you are doing? So, if anybody does plagiarize your program during a real interview process, they’d better have answers to those questions 🙂

    (2) 
  5. Daniel Gent

    edit: updated code as per Rashid’s comment

    Very interesting, here is my version (this problem seemed like a good excuse to use the REDUCE expression)

    REPORT zdg_butterfly_numbers.
    
    PARAMETERS pnum TYPE i OBLIGATORY DEFAULT 12.
    PARAMETERS ppad DEFAULT '_'.
    PARAMETERS psw  AS CHECKBOX DEFAULT 'X'.
    
    START-OF-SELECTION.
      cl_demo_output=>display_text(
        REDUCE string(
          LET
            n          = COND i( WHEN pnum GE 1 THEN pnum ELSE 1 )
            dimension  = ( ( n * 2 ) - 1 )
            cell_pad   = ppad
            same_width = psw
          IN
          INIT
            butterfly_string = VALUE string( )
            column_value     = VALUE i( )
            cell_content     = VALUE string( )
          FOR line   = 1 UNTIL line   > dimension
          FOR column = 1 UNTIL column > dimension
          NEXT
            column_value = ( abs( n - column ) + 1 )
            cell_content =
              |{
                COND #( WHEN abs( n - line ) LT column_value THEN |{ column_value }| )
                WIDTH = strlen( |{ COND #( WHEN same_width EQ abap_true THEN n ELSE column_value ) }| )
                PAD   = cell_pad
                ALIGN = RIGHT
              }|
            butterfly_string =
              butterfly_string &&
              cell_content     &&
              COND #( WHEN column EQ dimension THEN cl_abap_char_utilities=>newline )
        )
      ).

    .

    (2) 
    1. Rashid Javed Post author

      First of all, thanks for sharing your code. I never thought REDUCE was that powerful. Thats an excellent example.

      Second, for issue with write, you can hold the result in a string and later display that string using CL_DEMO_OUTPUT as below. It works but for number greater than 10, the formatting is out.

       

      REPORT zdg_butterfly_numbers.
      
      PARAMETERS pnum TYPE i OBLIGATORY DEFAULT 4.
      
      data: ls_result type string.  " Result String
      
      START-OF-SELECTION.
      
        ls_result =  REDUCE string(
          LET
            dim = ( 1 + ( ( pnum - 1 ) * 2 ) )
          IN
          INIT
            butterfly_string = VALUE string( )
            column_value     = VALUE i( )
            show_value       = VALUE abap_bool( )
            cell_value       = VALUE string( )
          FOR lin = 1 UNTIL lin > dim
          FOR col = 1 UNTIL col > dim
          NEXT
            column_value = ( abs( pnum - col ) + 1 )
            show_value   = COND #( WHEN abs( pnum - lin ) LT column_value THEN abap_true ELSE abap_false )
            cell_value   = COND #( WHEN show_value EQ abap_true THEN |{ column_value }| ELSE '0' )
            butterfly_string  = COND #( WHEN col EQ dim
              THEN butterfly_string && cell_value && cl_abap_char_utilities=>newline
              ELSE butterfly_string && cell_value
            )
        ).
      ** display results
       CALL METHOD cl_demo_output=>display_text
         EXPORTING
           text   = ls_result
           .

       

      (1) 
      1. Daniel Gent

        Never used the cl_demo_output class before. I guess this execise is also a good use to give that one a try. 🙂

        I’ve updated the code in my original post (format also doesn’t break so easily)

        (2) 
        1. Suhas Saha

          Daniel Gent – Niceeeeeee! I like it when people use functional ABAP 🙂

          Never used the cl_demo_output class before. I guess this execise is also a good use to give that one a try.

          Now that we are at it, you could use CL_DEMO_INPUT instead of the input PARAMETER.

          BR, Suhas

          (1) 
  6. André Brito

    Nice blog. Happy to see so many replies in such a short time. Congratulations.

    I would definitely introduce this exercise not in a interview but in a training session for starters.

    ABAP is a “business” programming language, which means that in real world this kind of scenario is not very likely to arise, but this exercise can help to identify those individuals with an eye for detail and logical thinking.

    I’m a big advocate for ABAP OO but would not use it for this exercise. Simply because it is overkill. People need to know which tool to use in each situation. You don’t just use a bazooka to kill a fly.  🙂

     

    Finally I present you my solution: 🙂

    REPORT test_butterfly_pattern.
    
    PARAMETERS pnum TYPE i DEFAULT 5 OBLIGATORY.
    
    START-OF-SELECTION.
    
      DO ( 2 * pnum - 1 ) TIMES.
        DATA(lv_col) = abs( pnum - sy-index ) + 1.
        DO ( 2 * pnum - 1 ) TIMES.
          WRITE COND char3( LET lv_printvalue = abs( pnum - sy-index ) + 1 IN
                           WHEN lv_printvalue GE lv_col THEN lv_printvalue ELSE space ).
        ENDDO.
        SKIP.
      ENDDO.
    
    (3) 
    1. Jelena Perfiljeva

      Finally someone who gets it! No disrespect to functional ABAP but I got a brain freeze looking at the REDUCE option above. As an science experiment – yes. As a productive code – no. Sorry.

      Big hug!

      (0) 

Leave a Reply