Knobelaufgabe ( Frühling 2023 )

Alles Rund um SAP®.
7 Beiträge • Seite 1 von 1
7 Beiträge Seite 1 von 1

Knobelaufgabe ( Frühling 2023 )

Beitrag von black_adept (Top Expert / 4080 / 125 / 934 ) »
Moin allerseits,
wie inzwischen üblich bin ich über ein Problem gestolpert, dessen Lösung mich dann inspiriert hat daraus eine Knobelaufgabe für dieses Forum zu erstellen.

Ich hatte das Problem, dass mir eine Range vorlag welche theoretisch hinreichend groß werden konnte, so dass ein Dump bei einem

Code: Alles auswählen.

SELECT .... field IN @range
zu befürchten war. Allerdings war diese Range so aufgebaut, dass auf Grund der Gegebenheiten sehr viele aufeinanderfolgende Einzelwerte ( I EQ ) in der Range vorkommen konnten ( nicht notwendigerweise nacheinander ), so dass man durch ein Verdichten in Intervalle die Range immer auf ein erträgliches Maß reduzieren konnte .

Und das ist dann auch die Aufgabe diesmal. Erstellt eine Utilitymethode mit folgender Signatur, in die man eine Range/Select-Option reinkippen kann und es kommt eine verdichtete Range zurück

Code: Alles auswählen.

CLASS-METHODS condense_range CHANGING ct_range TYPE ANY TABLE .
Folgende Vorgaben:
  • ● Die Methode sollte nicht dumpen, wenn Schrott übergeben wird. Aber ehrlich gesagt, wenn sie es doch tut weil ein Honk sie falsch füttert ist das zu verschmerzen. Aber es wäre nett, wenn das ein wenig abgefangen und die Range einfach unverändert zurückgegeben wird
  • ● Sie sollte mit beliebigen SIGN/OPTION Kombinationen umgehen können. Die resultierende Range muss bei einem SELECT halt nur das gleiche Resultat liefern
  • ● Sie solle mit allem umgehen können, wo man einen "Nachfolger" erwarten würde. Mir fallen direkt Integerzahlen, NUMC-Felder, Belegnummern ( auch wenn VBELN ein CHAR-Feld ist und es theoretisch auch nicht rein nummerische Belege geben könnte ) und Datumsfelder ein
  • ● Wahrscheinlich offensichtlich, aber ich erwähne es noch mal explizit. Die resultierende Range sollte auf keinen Fall mehr Zeilen haben als die Quellrange
  • ● Die Methode muss nicht alles können. Wenn sie "nur" I EQ Eingaben verdichtet ist das auch eine Lösung, da das der zu erwartende Standardfall ist, aber wenn sie mehr kann ist das auch nicht zu verachten
  • ● Für diejenigen, für die das obige alles viel zu einfach ist ( also Nerds ) gleich eine Zusatzaufgabe: Wenn die Signatur zusätzliche [ evtl. optionale] Parameter für einen maximalen und minimalen Wert hätte, könnte man einen völlig anderen Ansatz verwenden. Warum und wie könnte der alternative Ansatz dann aussehen?
Im Gegensatz zu früheren Aufgaben gibt es hier kein "richtig oder falsch" sondern einfach mögliche Ansätze.
Wenn jemand das am lebenden Objekt testen möchte, kann man es dazu verwenden um sich einen Eindruck über die Arbeitstage bei SAP zu verschaffen. Beispiel siehe folgendes Programm.

Code: Alles auswählen.

REPORT.

END-OF-SELECTION.
  SELECT 'I'  AS sign,
         'EQ' AS option,
         cdat AS low,
         cdat AS high
    FROM reposrc
    WHERE cnam = 'SAP'
    INTO TABLE @DATA(gt_range).

  zcl_utilities=>condense_range( CHANGING ct_range = gt_range ).

  cl_salv_table=>factory( IMPORTING
                            r_salv_table   = DATA(go_salv)    " Basisklasse einfache ALV Tabellen
                          CHANGING
                            t_table        = gt_range ).
  go_salv->display( ).
Hätte man das erwartet??

Folgende Benutzer bedankten sich beim Autor black_adept für den Beitrag:
ewx

live long and prosper
Stefan Schmöcker

email: stefan@schmoecker.de

gesponsert
Stellenangebote auf ABAPforum.com schalten
kostenfrei für Ausbildungsberufe und Werksstudenten


Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von a-dead-trousers (Top Expert / 4394 / 223 / 1182 ) »
Dein Test-Coding ist falsch. HIGH darf bei EQ nicht befüllt werden.
Theory is when you know something, but it doesn't work.
Practice is when something works, but you don't know why.
Programmers combine theory and practice: Nothing works and they don't know why.

ECC: 6.18
Basis: 7.50

Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von black_adept (Top Expert / 4080 / 125 / 934 ) »
a-dead-trousers hat geschrieben:
20.03.2023 13:14
Dein Test-Coding ist falsch. HIGH darf bei EQ nicht befüllt werden.
Jein. Es ist definitiv nicht schön, aber HIGH ist bei EQ nicht verboten sondern wird einfach ingoriert. Denn das Feld ist ja immer in der Range vorhanden, ist aber üblicherweise bei EQ initial gefüllt. Aber der SELECT inkl. HIGH macht es halt so einfach die korrekte Ausgabestruktur zu erzeugen. Die eigentlich saubere Version mittels sql-Funktion CAST ist auf älteren Releases allerdings noch nicht für Datumsfelder möglich, wohingegen das von mir gepostete Konstrukt auf nahezu allen halbwegs modernen Releases lauffähig sein sollte.

Folgende Benutzer bedankten sich beim Autor black_adept für den Beitrag:
a-dead-trousers

live long and prosper
Stefan Schmöcker

email: stefan@schmoecker.de

Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von edwin (Specialist / 302 / 10 / 68 ) »
Hi,
das Problem habe ich auch schon gehabt ( Matnr > 500000 aus einer Excel Liste in die Selektion ), das war (etwas angepasst) meine Lösung damals :

Code: Alles auswählen.

METHOD condense_range.
  FIELD-SYMBOLS <fs_any>      TYPE any.
  FIELD-SYMBOLS <fsign>       TYPE any.
  FIELD-SYMBOLS <foption>     TYPE any.
  FIELD-SYMBOLS <flow>        TYPE any.
  FIELD-SYMBOLS <fhigh>       TYPE any.

  FIELD-SYMBOLS <fs>          TYPE any.
  FIELD-SYMBOLS <fo>          TYPE any.
  FIELD-SYMBOLS <fl>          TYPE any.
  FIELD-SYMBOLS <fh>          TYPE any.

  FIELD-SYMBOLS <ftable>    TYPE STANDARD TABLE.
  FIELD-SYMBOLS <fline>     TYPE any.

  DATA  ref_line              TYPE REF TO data.
  DATA  ref_table             TYPE REF TO data.

  FIELD-SYMBOLS <fsrtable>    TYPE STANDARD TABLE.
  FIELD-SYMBOLS <fsrline>     TYPE any.
  DATA  ref_elem              TYPE REF TO cl_abap_elemdescr.
  DATA  wdfies                TYPE dfies.
  DATA  fbname                TYPE progname.
  DATA  mask                  TYPE string.


* Check gefüllt
  IF ct_range[] IS INITIAL.    RETURN.  ENDIF.

* Check ist eine Standard Tabelle
  ASSIGN ct_range TO <ftable>.
  IF sy-subrc NE 0. RETURN. ENDIF.

  READ TABLE <ftable> ASSIGNING <fline> INDEX 1.
  IF sy-subrc NE 0. RETURN. ENDIF.

* Check gibt es die SIGN/OPTION/LOW/HIGH Felder
  ASSIGN COMPONENT 'SIGN'    OF STRUCTURE <fline> TO <fsign>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  ASSIGN COMPONENT 'OPTION'  OF STRUCTURE <fline> TO <foption>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  ASSIGN COMPONENT 'LOW'     OF STRUCTURE <fline> TO <flow>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  ASSIGN COMPONENT 'HIGH'    OF STRUCTURE <fline> TO <fhigh>.
  IF sy-subrc NE 0. RETURN. ENDIF.


  TRY.
      CREATE DATA ref_line LIKE LINE OF <ftable>.
    CATCH cx_root.
      RETURN.
  ENDTRY.

  ASSIGN ref_line->* TO <fsrline>.

  ASSIGN COMPONENT 'SIGN'   OF STRUCTURE <fsrline> TO <fs>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  ASSIGN COMPONENT 'OPTION' OF STRUCTURE <fsrline> TO <fo>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  <fo> = 'BT'.
  ASSIGN COMPONENT 'LOW'    OF STRUCTURE <fsrline> TO <fl>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  ASSIGN COMPONENT 'HIGH'   OF STRUCTURE <fsrline> TO <fh>.
  IF sy-subrc NE 0. RETURN. ENDIF.
  
* Hole Datenelement info
  TRY.
      ref_elem ?= cl_abap_datadescr=>describe_by_data( p_data = <fl> ).
    CATCH cx_root.
      RETURN.
  ENDTRY.

* Conversion Exit ?
  IF ref_elem->is_ddic_type( ) = 'X'.
    TRY.
        wdfies = ref_elem->get_ddic_field( ).
      CATCH cx_root.
        CLEAR wdfies.
    ENDTRY.
  ENDIF.

  TRY.
      CREATE DATA ref_table LIKE TABLE OF <fline>.
    CATCH cx_root.
      RETURN.
  ENDTRY.
  ASSIGN ref_table->* TO <fsrtable>.

  SORT ct_range BY ('LOW') ASCENDING.
  LOOP AT ct_range ASSIGNING <fline>.

    ASSIGN COMPONENT 'SIGN'    OF STRUCTURE <fline> TO <fsign>.
    IF <fsign> NE 'I'.           CONTINUE. ENDIF.
    ASSIGN COMPONENT 'OPTION'  OF STRUCTURE <fline> TO <foption>.
    IF <foption> NE 'EQ'.        CONTINUE. ENDIF.
    ASSIGN COMPONENT 'LOW'     OF STRUCTURE <fline> TO <flow>.
    ASSIGN COMPONENT 'HIGH'    OF STRUCTURE <fline> TO <fhigh>.
    clear <fhigh>.


    IF <fsrtable> IS NOT INITIAL.
      IF <fl> IN <fsrtable>.
        DELETE ct_range.
        CONTINUE.
      ENDIF.
    ENDIF.

    IF <fs> IS INITIAL.
       <fs> = 'I'.
       <fl> = <flow>.
       <fh> = <flow>.
    ENDIF.

    IF <flow> BETWEEN <fl> AND <fh>.
      DELETE ct_range.
      CONTINUE.
    ENDIF.

    TRY.
        <fh> = <fh> + 1.
      CATCH cx_root.
        CONTINUE.
    ENDTRY.

    CASE wdfies-convexit.
      WHEN ' '.
      WHEN 'ALPHA'.        <fh> = |{ <fh> ALPHA = IN }|.
      WHEN OTHERS.
* FÜR MATNR funktioniert es (bei uns) - aber sicher nicht überall
        SHIFT <fh> LEFT DELETING LEADING ' 0'.
        TRY.
            fbname = |CONVERSION_EXIT_{ wdfies-convexit }_INPUT|.
            CALL FUNCTION fbname
              EXPORTING
                input  = <fh>
              IMPORTING
                output = <fh>.
          CATCH cx_root.
        ENDTRY.
    ENDCASE.

    IF <flow> BETWEEN <fl> AND <fh>.
      DELETE ct_range.
      CONTINUE.
    ENDIF.

    TRY.
        <fh> = <fh> - 1.
      CATCH cx_root.
    ENDTRY.
    CASE wdfies-convexit.
      WHEN ' '.
      WHEN 'ALPHA'.                <fh> = |{ <fh> ALPHA = IN }|.
      WHEN OTHERS.
        SHIFT <fh> LEFT DELETING LEADING ' 0'.
        TRY.
            fbname = |CONVERSION_EXIT_{ wdfies-convexit }_INPUT|.
            CALL FUNCTION fbname
              EXPORTING
                input  = <fh>
              IMPORTING
                output = <fh>.
          CATCH cx_root.
        ENDTRY.
    ENDCASE.

    IF <fl> = <fh>.
      <fo> = 'EQ'.
      CLEAR <fh>.
    ENDIF.

    APPEND <fsrline> TO <fsrtable>.
    <fl> = <flow>.
    <fh> = <flow>.
    <fo> = 'BT'.
    DELETE ct_range.
  ENDLOOP.

  IF <fl> = <fh>.
    <fo> = 'EQ'.
    CLEAR <fh>.
  ENDIF.

  APPEND <fsrline> TO <fsrtable>.

  APPEND LINES OF <fsrtable> TO ct_range.
  CLEAR <fsrtable>. FREE <fsrtable>.
  CLEAR <fsrline>. FREE <fsrline>.

ENDMETHOD.
Grüße Edwin

Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von black_adept (Top Expert / 4080 / 125 / 934 ) »
Moin allerseits,

etwas verspätet kommt nun eine "Lösung" zu dieser Aufgabe, die ich aus den Einsendungen zusammengebaut habe und eine Erläuterung wie man dazu kommt.

Falls jemand einen pragmatischen Ansatz sucht, so sollte man sich Edwins Lösung anschauen, welche für den zu erwartenden Standardfall mit einer üblichen Größenordnung sehr gut funktioniert. Ich hoffe, dass murdock seine Lösung, die er mir via PM geschickt hatte so oder etwas überarbeitet postet, da seine Lösung mit größeren Ranges umgehen kann, was aber mit einer höheren Codekomplexität als edwins Lösung einhgeht.

Die von mir gleich gepostete Lösung ist dann noch etwas allgemeiner, was die Lesbarkeit des Codes noch mehr verringert, einfach weil viel mehr mit Feldsymbolen gearbeitet werden muss.

Vorüberlegung 1: Nahezu alle OPTIONS in Ranges beziehen sich auf Intervalle. Diese sind:
  • BT - Dies ist der einfachste Fall, da es sich hier um ein normales, geschlossenes Intervall handelt
  • EQ - Das ist der zweite zu erwartende Standardfall einer von Hand gefüllten Selektionsoption. Man kann eine EQ-Option aber leicht als entartetes Intervall auffassen. EQ xxx ist dann gleich BT xxx xxx
  • GE und LE: Dies sind mathematisch gesehen zwei halboffene Intervalle mit einer festen Grenze.
    Aber da jede Variable Maximalwerte und Minimalwerte in seinen Feldern( Danke an a-d-t für den Tipp, wie man die typgerecht ermittelt ) definiert, kann man auch diese in geschlossene Intervalle umwandeln: GE xxx ist gleich BT xxx MAX und LE yyy ist gleich BT MIN yyy
  • GT und LT sind offene Intervalle. Und hier kommt der Teil der Voraussetzung ins Spiel, der sich darauf bezieht, dass die Werte ermittelbare Nachfolger haben sollen. Hierdurch wird es möglich auch diese in geschlossene Intervalle umzuwandeln: GT xxx ist gleich BT Nachfolger(xxx) MAX
    und LT yyy ist gleich BT MIN Vorgänger(yyy)
  • NB und NE Genaugenommen entspricht dies "Alles außerhalb eines Intervalls" Leider ist es nicht möglich I NE z in E EQ z oder analog I NB xxx yyy in E BT xxx yyy umzuwandeln, da das SIGN = E erst nach dem I abgehandelt wird und bei hinreichend komplexen Ranges ist das dann nicht mehr gleich. Man könnte alternativ auch I NE X in zwei Intervalle I LE X + I GE X umwandeln - aber das widerspricht der Vorgabe, dass die resultierende Range definiv kleiner sein sollte.
    In der Theorie könnte das zwar schlussendlich zu einer trotzdem reduzierten Range führen, aber die Überlegungen bzw. das Coding um das auszutesten ist zu komplex, als dass ich es versucht hätte.
  • CP und NPPattern sind zu komplex um sie in Intervalle überführen zu können
Vorüberlegung 2: Wie kann man eine Anzahl von BT-Intervallen komprimieren. Es gibt hier im Wesentlichen folgende Fälle, wenn man zwei Intervalle I1 und I2 betrachtet:
  • Eins der beiden Intervalle I1 umfasst das andere I2. In diesem Fall kann das innere Intervall I2 einfach ignoriert werden --> Reduktion der Range
  • Die beiden Intervalle überschneiden sich. Beispiel I1 = [5,10] und I2 = [7,20]. Dann kann man ein Gesamtintervall bilden, welches die beiden beinhaltet. Im Beispiel wäre das dann [5,20]
  • Die Intervalle sind disjunkt Hier kann ein zweites Mal die Vorgabe, dass es Nachfolger geben muss ausgespielt werden. Falls die untere Grenze des einen Intervalls der Nachfolger der oberen Grenze des anderen Intervalls ist, kann man die beiden Intervalle "zusammenkleben" wie im vorherigen Fall. Beispiel: I1 = [5,10], I2 = [11,15] --> Die beiden Intervalle können durch [5,15] ersetzt werden.
Vorüberlegung 3: Abmischen von I BT und E BT Intervallen. Auch hier führt wieder eine Fallunterscheidung zum Ziel. Hier sei noch zu beachten, dass es im Fall, dass alle zu betrachtenden Ranges nach der Umwandlung aus Vorüberlegung 1 in BT-Intervalle überführt werden konnten, spezielle Reduktionen möglich sind, da dann mit dem I BT Anteil eine Menge von disjunkten Intervallen definiert ist, von denen dann eine Menge disjunkter E BT Intervalle weggenommen werden. Dies sei der Spezialfall, welcher aber in der Praxis häufig auftreten kann, so das das hier berücksichtigt wird.
  • E BT ist disjunkt zu allen I BT Intervallen:Falls der Spezialfall vorliegt, ist dieses E BT Intervall überflüssig, da es nichts reduziert, was mit den I BT Intervallen selektiert wird
  • E BT überlappt ein I BT IntervallEgal ob Spezialfall oder nicht - in diesem Fall dann das I BT Intervall entsorgt werden, da alles was durch das I BT selektiert wird nachher durch das E BT wieder herausgenommen wird
  • Das E BT Intervall schneidet das I BT IntervallIm Spezialfall kann dann das I BT Intervall durch Anpassen einer Grenze reduziert werden. Beispiel
    I=[5,20] E=[15,30] --> Man kann I durch [5,Vorgänger(15)] = [5,14] ersetzen.
    Wenn man das für alle I-Intervalle getan hat, kann danch das E-Intervall entsorgt werden
  • E BT liegt innerhalb I BTHier ist keine wesentliche Reduktion möglich
Und das war es dann am Ende auch schon der Algorithmus um das Problem zu lösen. Vielleicht sollte man darauf hinweisen, dass bei einer allgemeinen Lösung eine Menge dynamischer Aktionen nötig sind ( Sortierung, die Felder können nicht mit mit SIGN oder LOW angesprochen werden, da die Ranges anonym als STANDARD TABLE oder REF TO DATA übergeben müssen ) und dass für die Ermittlung des Nachfolgers Typinformationen bekannt sein müssen. Gerade für den letzten Fall möchte ich auf murdocks Lösung hinweisen, die diese Komplexität mit Umwandlung in den type RSSELOPTION elegant umschifft und damit eine Menge der im allgemeinen Fall nötigen dynamischen Anweisung umgeht und dadurch de facto nur LT und GT nicht abgehandelt werden können und der Spezialfall schneidender I BT und E BT Intervalle nicht reduziert werden kann. Aber diese beiden Fälle werden in der freien Wildbahn wohl eher seltener vorkommen.

Hier dann eine allgemeine Lösung

Code: Alles auswählen.

REPORT zknobel_condense_rangeb.

CLASS lcl_knobel DEFINITION FINAL.
  PUBLIC SECTION.
    CLASS-METHODS: condense_range CHANGING ct_range TYPE STANDARD TABLE.
  PRIVATE SECTION.
    CLASS-METHODS:
      can_be_condensed   IMPORTING it_range                   TYPE STANDARD TABLE
                         RETURNING VALUE(rv_can_be_condensed) TYPE abap_bool,
      partition          IMPORTING it_range        TYPE STANDARD TABLE
                                   iv_typekind     TYPE abap_typekind
                         CHANGING  ct_range_sign_i TYPE STANDARD TABLE
                                   ct_range_sign_e TYPE STANDARD TABLE
                                   ct_range_wtf    TYPE STANDARD TABLE,
      get_successor      IMPORTING iv_typekind TYPE abap_typekind
                         CHANGING  cv_value    TYPE any,
      get_predecessor    IMPORTING iv_typekind TYPE abap_typekind
                         CHANGING  cv_value    TYPE any,
      condense_bt_range  IMPORTING iv_typekind TYPE abap_typekind
                         CHANGING  ct_bt_range TYPE STANDARD TABLE,
      merge_ibt_ebt      IMPORTING iv_typekind   TYPE abap_typekind
                                   it_range_wtf  TYPE STANDARD TABLE
                         CHANGING  ct_bt_range_i TYPE STANDARD TABLE
                                   ct_bt_range_e TYPE STANDARD TABLE.
ENDCLASS.

END-OF-SELECTION.
*TABLES: vbak.
*SELECT-OPTIONS: s_vbeln FOR vbak-vbeln.
*  DATA(gt_range) = s_vbeln[].
*  lcl_knobel=>condense_range( CHANGING ct_range = gt_range ).
*
*  SELECT * FROM vbak WHERE vbeln IN @s_vbeln   ORDER BY vbeln  INTO TABLE @DATA(lt_data1).
*  SELECT * FROM vbak WHERE vbeln IN @gt_range ORDER BY vbeln  INTO TABLE @DATA(lt_data2).
*  ASSERT lt_data1 = lt_data2.
  SELECT 'I'  AS sign,
         'EQ' AS option,
         cdat AS low,
         cdat AS high
    FROM reposrc
    WHERE cnam = 'SAP'
    INTO TABLE @DATA(gt_range).
  lcl_knobel=>condense_range( CHANGING ct_range = gt_range ).

  SORT gt_range BY low.
  TRY.
      cl_salv_table=>factory( IMPORTING r_salv_table = DATA(go_salv)    " Basisklasse einfache ALV Tabellen
                              CHANGING  t_table      = gt_range ).
      go_salv->get_display_settings( )->set_list_header( CONV #( |{ 'Einträge in Range:'(001) } { lines( gt_range ) }| ) ).
      go_salv->get_functions( )->set_all( ).
      go_salv->display( ).
    CATCH cx_salv_msg INTO DATA(lo_cx).
      MESSAGE lo_cx TYPE 'I' DISPLAY LIKE 'E'.
  ENDTRY.



CLASS lcl_knobel IMPLEMENTATION.
  METHOD condense_range.

    DATA:lr_data     TYPE REF TO data,
         lv_typekind TYPE abap_typekind.
    FIELD-SYMBOLS: <lt_r_sign_i> TYPE STANDARD TABLE,
                   <lt_r_sign_e> TYPE STANDARD TABLE,
                   <lt_r_wtf>    TYPE STANDARD TABLE,
                   <ls_range>    TYPE any,
                   <lv_sign>     TYPE char1,
                   <lv_option>   TYPE char2,
                   <lv_low>      TYPE any,
                   <lv_high>     TYPE any.

*--------------------------------------------------------------------*
* 1. Muss überhaupt was getan werden?
*--------------------------------------------------------------------*
    IF can_be_condensed( ct_range ) = abap_false.
      RETURN.
    ENDIF.

*--------------------------------------------------------------------*
* 2. Ein paar Vorbereitungen und dann normieren
*    Normieren = alles was möglich ist nach BT umwandeln
*--------------------------------------------------------------------*
    CREATE DATA lr_data LIKE LINE OF ct_range.
    ASSIGN lr_data->* TO <ls_range>.
    ASSIGN COMPONENT 'SIGN  ' OF STRUCTURE <ls_range>  TO <lv_sign>   .
    ASSIGN COMPONENT 'OPTION' OF STRUCTURE <ls_range>  TO <lv_option> .
    ASSIGN COMPONENT 'LOW   ' OF STRUCTURE <ls_range>  TO <lv_low>    .
    ASSIGN COMPONENT 'HIGH  ' OF STRUCTURE <ls_range>  TO <lv_high>   .

    lv_typekind = cl_abap_typedescr=>describe_by_data( <lv_low> )->type_kind.

    CREATE DATA lr_data LIKE ct_range.  ASSIGN lr_data->* TO <lt_r_sign_i>.
    CREATE DATA lr_data LIKE ct_range.  ASSIGN lr_data->* TO <lt_r_sign_e>.
    CREATE DATA lr_data LIKE ct_range.  ASSIGN lr_data->* TO <lt_r_wtf>.

    partition( EXPORTING it_range        = ct_range
                         iv_typekind     = lv_typekind
               CHANGING  ct_range_sign_i = <lt_r_sign_i>
                         ct_range_sign_e = <lt_r_sign_e>
                         ct_range_wtf    = <lt_r_wtf>
                       ).

*--------------------------------------------------------------------*
* 3.  Die einzelnen Teile intern optimieren und dann zusammenfügen
*--------------------------------------------------------------------*
    CLEAR ct_range.
    APPEND LINES OF <lt_r_wtf> TO ct_range.  " All das was nicht optimiert werden konnte
* I BT verdichten
    condense_bt_range( EXPORTING iv_typekind = lv_typekind
                       CHANGING  ct_bt_range = <lt_r_sign_i> ).
* E BT verdichten
    condense_bt_range( EXPORTING iv_typekind = lv_typekind
                       CHANGING  ct_bt_range = <lt_r_sign_e> ).
* I BT und E BT abmischen
    merge_ibt_ebt( EXPORTING iv_typekind   = lv_typekind
                             it_range_wtf  = <lt_r_wtf>
                   CHANGING  ct_bt_range_i = <lt_r_sign_i>
                             ct_bt_range_e = <lt_r_sign_e> ).

    APPEND LINES OF <lt_r_sign_i> TO ct_range.  " All das was nicht optimiert werden konnte
    APPEND LINES OF <lt_r_sign_e> TO ct_range.  " All das was nicht optimiert werden konnte

*--------------------------------------------------------------------*
* Am Ende "Hübsch" machen BT mit HIGH = LOW --> EQ
*--------------------------------------------------------------------*
    LOOP AT ct_range ASSIGNING FIELD-SYMBOL(<ls_range_beautify>).
      <ls_range> = <ls_range_beautify>.
      IF    <lv_option> = 'BT'
        AND <lv_low>    = <lv_high>.
        <lv_option> = 'EQ'.
        CLEAR <lv_high>.
        <ls_range_beautify> = <ls_range>.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.

  METHOD condense_bt_range.

    DATA: lr_data TYPE REF TO data.
    FIELD-SYMBOLS: <lv_low>       TYPE any,
                   <lv_high>      TYPE any,
                   <lv_last_low>  TYPE any,
                   <lv_last_high> TYPE any,
                   <lv_value>     TYPE any.

*--------------------------------------------------------------------*
* Duplikate entfernen und in sortierte Reihenfolge bringen
*--------------------------------------------------------------------*
    SORT ct_bt_range BY ('LOW') ('HIGH').
    DELETE ADJACENT DUPLICATES FROM ct_bt_range COMPARING ALL FIELDS.

    LOOP AT ct_bt_range ASSIGNING FIELD-SYMBOL(<ls_row>).

      AT FIRST.
        ASSIGN <ls_row> TO FIELD-SYMBOL(<ls_last_row>).
        CONTINUE.
      ENDAT.

      ASSIGN COMPONENT 'LOW'  OF STRUCTURE  <ls_row>      TO <lv_low>.
      ASSIGN COMPONENT 'LOW'  OF STRUCTURE  <ls_last_row> TO <lv_last_low>.
      ASSIGN COMPONENT 'HIGH' OF STRUCTURE  <ls_row>      TO <lv_high>.
      ASSIGN COMPONENT 'HIGH' OF STRUCTURE  <ls_last_row> TO <lv_last_high>.
      IF <lv_value> IS NOT ASSIGNED.
        CREATE DATA lr_data LIKE <lv_low>.
        ASSIGN lr_data->* TO <lv_value>.
      ENDIF.

*--------------------------------------------------------------------*
* Low diese Zeile <= high letze Zeile --> Letzte Zeile bekommen den höheren HIGH-Wert der beiden, aktuelle Zeile wird gelöscht
*--------------------------------------------------------------------*
      IF <lv_low> <= <lv_last_high>.
        IF <lv_high> > <lv_last_high>.
          <lv_last_high> = <lv_high>.
        ENDIF.
        DELETE ct_bt_range.  " SLIN Warnung - aber ich weiß ja was ich tue *hüstel*
        CONTINUE.
      ENDIF.

*--------------------------------------------------------------------*
* Low diese Zeile = Nachfolger high letze Zeile + --> Zusammenkleben
*--------------------------------------------------------------------*
      <lv_value> = <lv_last_high>.
      get_successor( EXPORTING iv_typekind = iv_typekind
                     CHANGING  cv_value    = <lv_value> ).
      IF <lv_low> = <lv_value>.
        <lv_last_high> = <lv_high>.
        DELETE ct_bt_range.  " SLIN Warnung - aber ich weiß ja was ich tue *hüstel*
        CONTINUE.
      ENDIF.

      ASSIGN <ls_row> TO <ls_last_row>.
    ENDLOOP.
  ENDMETHOD.

  METHOD merge_ibt_ebt.


*--------------------------------------------------------------------*
* Es gibt 5 Fälle für Fälle in E BT
*    1 -   E BT disjunkt zu allen  I BT Intervall     --> E BT kann entsorgt werden falls keine Patterns vorhanden sind
*    2 -   E BT überlappt I BT Intervall              --> I BT kann entsorgt werden
*    3 -   E BT schneidet I BT Intervall von unten    --> Reduzierung der SelOpts möglich falls keine Patterns vorhanden sind I BT reduzieren, E BT entsorgen
*    4 -   E BT schneidet I BT Intervall von oben     --> Reduzierung der SelOpts möglich falls keine Patterns vorhanden sind I BT reduzieren, E BT entsorgen
*    5 -   E BT innerhalb I BT Intervall              --> keine wesentliche Reduzierung der SelOpts möglich
*--------------------------------------------------------------------*

*--------------------------------------------------------------------*
* Der geschachtelte Loop könnte evtl. optimiert werden, da beide Tabellen sortiert sind,
* Aber wenn beide nicht arg groß sind, ist es besser zu lesen
*--------------------------------------------------------------------*
    LOOP AT ct_bt_range_e ASSIGNING FIELD-SYMBOL(<ls_range_e>).

      ASSIGN COMPONENT 'LOW'  OF STRUCTURE <ls_range_e> TO FIELD-SYMBOL(<lv_low_e>).
      ASSIGN COMPONENT 'HIGH' OF STRUCTURE <ls_range_e> TO FIELD-SYMBOL(<lv_high_e>).

      LOOP AT ct_bt_range_i ASSIGNING FIELD-SYMBOL(<ls_range_i>). "#EC CI_NESTED

        ASSIGN COMPONENT 'LOW'  OF STRUCTURE <ls_range_i> TO FIELD-SYMBOL(<lv_low_i>).
        ASSIGN COMPONENT 'HIGH' OF STRUCTURE <ls_range_i> TO FIELD-SYMBOL(<lv_high_i>).
* Fall 2
        IF    <lv_low_e>  <= <lv_low_i>
          AND <lv_high_e> >= <lv_high_i>.
          DELETE ct_bt_range_i.
          CONTINUE.
        ENDIF.
* Fall 3 und 4:  Wenn Keine Patterns vorhanden sind, kann die I BT angepasst werden und E BT entsorgt werden, da die vollst. Range aus Intervallen besteht
        IF it_range_wtf IS INITIAL.
* Fall 3 - Schnitt von unten
          IF    <lv_low_e>  <= <lv_low_i>
            AND <lv_high_e> >= <lv_low_i>.
            <lv_low_i> = <lv_high_e>.
            get_successor( EXPORTING iv_typekind = iv_typekind
                           CHANGING  cv_value    = <lv_low_i> ).
            DELETE ct_bt_range_e.
            EXIT.
          ENDIF.
* Fall 4 - Schnitt von oben
          IF    <lv_low_e>  <= <lv_high_i>
            AND <lv_high_e> >= <lv_high_i>.
            <lv_high_i> = <lv_low_e>.
            get_predecessor( EXPORTING iv_typekind = iv_typekind
                             CHANGING  cv_value    = <lv_high_i> ).
            DELETE ct_bt_range_e.
            EXIT.
          ENDIF.
        ENDIF.

      ENDLOOP.
    ENDLOOP.

*--------------------------------------------------------------------*
* Jetzt kann der 1. Fall angegangen werden
* Nach den obigen Aktionen sind die I BT und E BT Intervalle überlappungsfrei oder E BT liegt vollständig innerhalb von I BT.
* Falls keine
*--------------------------------------------------------------------*
    DATA: lv_e_bt_in_i_bt TYPE abap_bool.
    IF it_range_wtf IS INITIAL.
      LOOP AT ct_bt_range_e ASSIGNING <ls_range_e>.

        ASSIGN COMPONENT 'LOW'  OF STRUCTURE <ls_range_e> TO <lv_low_e>.
        ASSIGN COMPONENT 'HIGH' OF STRUCTURE <ls_range_e> TO <lv_high_e>.
        lv_e_bt_in_i_bt = abap_false.
        LOOP AT ct_bt_range_i ASSIGNING <ls_range_i>. "#EC CI_NESTED

          ASSIGN COMPONENT 'LOW'  OF STRUCTURE <ls_range_i> TO <lv_low_i>.
          ASSIGN COMPONENT 'HIGH' OF STRUCTURE <ls_range_i> TO <lv_high_i>.

          IF    <lv_low_e>  >= <lv_low_i>
            AND <lv_high_e> <= <lv_high_i>.
            lv_e_bt_in_i_bt = abap_true.
            EXIT.
          ENDIF.

        ENDLOOP.
        IF lv_e_bt_in_i_bt = abap_false.
          DELETE ct_bt_range_e.
        ENDIF.
      ENDLOOP.
    ENDIF.
  ENDMETHOD.

  METHOD partition.

    DATA:lr_data     TYPE REF TO data.
    FIELD-SYMBOLS: <ls_range>  TYPE any,
                   <lv_sign>   TYPE char1,
                   <lv_option> TYPE char2,
                   <lv_low>    TYPE any,
                   <lv_high>   TYPE any,
                   <lv_min>    TYPE any,
                   <lv_max>    TYPE any.


    LOOP AT it_range ASSIGNING FIELD-SYMBOL(<ls_range_split>).

      AT FIRST.

        CREATE DATA lr_data LIKE <ls_range_split>.
        ASSIGN lr_data->* TO <ls_range>.

        ASSIGN COMPONENT 'SIGN  ' OF STRUCTURE <ls_range>  TO <lv_sign>   .
        ASSIGN COMPONENT 'OPTION' OF STRUCTURE <ls_range>  TO <lv_option> .
        ASSIGN COMPONENT 'LOW   ' OF STRUCTURE <ls_range>  TO <lv_low>    .
        ASSIGN COMPONENT 'HIGH  ' OF STRUCTURE <ls_range>  TO <lv_high>   .

        lr_data = cl_abap_exceptional_values=>get_min_value( in = <lv_low> ).
        ASSIGN lr_data->* TO <lv_min>.
        lr_data = cl_abap_exceptional_values=>get_max_value( in = <lv_low> ).
        ASSIGN lr_data->* TO <lv_max>.
        IF iv_typekind = 'C'.
          CLEAR <lv_min> WITH '0'.
          CLEAR <lv_max> WITH '9'.
        ENDIF.


      ENDAT.


      <ls_range> = <ls_range_split>.

      CASE <lv_option>.

        WHEN 'EQ'." EQ -> BT mit High = low
          <lv_option> = 'BT'.
          <lv_high>   = <lv_low>.

        WHEN 'GE'. " GE --> BT mit high = max value
          <lv_option> = 'BT'.
          <lv_high>   = <lv_max>.

        WHEN 'GT'. " Wie GE, aber low = Nachfolger
          <lv_option> = 'BT'.
          <lv_high>   = <lv_max>.
          get_successor( EXPORTING iv_typekind = iv_typekind
                         CHANGING  cv_value    = <lv_low> ).

        WHEN 'LE'. " LE --> BT mit low = min
          <lv_option> = 'BT'.
          <lv_high>   = <lv_low>.
          <lv_low>   = <lv_min>.

        WHEN 'LT'. " Wie LE, aber High = Vorgänger
          <lv_option> = 'BT'.
          <lv_high>   = <lv_low>.
          get_predecessor( EXPORTING iv_typekind = iv_typekind
                           CHANGING  cv_value    = <lv_high> ).
          <lv_low>   = <lv_min>.

        WHEN 'BT'. " aleady ok

*        WHEN 'NB'.  " Alles außerhalb eines Intervalls --> !! I NB ungleich E BT wenn man die komplette Range betrachtet
*        WHEN 'NE'.  " Alles außer einem Wert           --> !! I NE ungleich E EQ wenn man die komplette Range betrachtet
*        WHEN 'CP'.  " Patterns are too complex -> leave as is
*        WHEN 'NP'.  " Patterns are too complex -> leave as is
        WHEN OTHERS.
          APPEND <ls_range> TO ct_range_wtf.
          CONTINUE.
      ENDCASE.

      CASE <lv_sign>.
        WHEN 'I'.
          APPEND <ls_range> TO ct_range_sign_i.
        WHEN 'E'.
          APPEND <ls_range> TO ct_range_sign_e.
        WHEN OTHERS.
          ct_range_wtf = it_range.
          CLEAR: ct_range_sign_i,
                 ct_range_sign_e.
          RETURN.  " Das darf nicht sein --> Tabelle ungeändert zurückgeben
      ENDCASE.

    ENDLOOP.
  ENDMETHOD.

  METHOD get_successor.
    CASE iv_typekind.
      WHEN 'C'. " Numeric field --> use ALPHA-conversion
        IF cv_value CO '0123456789'.
          cv_value = cv_value + 1.
          cv_value = |{ cv_value ALPHA = IN }|.
        ENDIF.
      WHEN OTHERS.
        cv_value = cv_value + 1.
    ENDCASE.
  ENDMETHOD.

  METHOD get_predecessor.
    CASE iv_typekind.
      WHEN 'C'. " Numeric field --> use ALPHA-conversion
        IF cv_value CO '0123456789'.
          cv_value = cv_value - 1.
          cv_value = |{ cv_value ALPHA = IN }|.
        ENDIF.
      WHEN OTHERS.
        cv_value = cv_value - 1.
    ENDCASE.
  ENDMETHOD.

  METHOD can_be_condensed.
    rv_can_be_condensed = abap_false.
*--------------------------------------------------------------------*
* Leere Tabelle ungeändert zurückgeben , Range mit 1 Zeile kann im allgemeinen nicht optimiert werden
*--------------------------------------------------------------------*
    IF lines( it_range ) <= 1.
      RETURN.
    ENDIF.
*--------------------------------------------------------------------*
* Ist es eine Range-Tabelle?
*--------------------------------------------------------------------*
    LOOP AT CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( it_range[  1 ] ) )->components ASSIGNING FIELD-SYMBOL(<ls_component>) .
      CASE sy-tabix.
        WHEN 1.
          IF <ls_component>-name <> 'SIGN'.   RETURN. ENDIF.
        WHEN 2.
          IF <ls_component>-name <> 'OPTION'. RETURN. ENDIF.
        WHEN 3.
          IF <ls_component>-name <> 'LOW'.    RETURN. ENDIF.
        WHEN 4.
          IF <ls_component>-name <> 'HIGH'.   RETURN. ENDIF.
        WHEN OTHERS.
          RETURN.
      ENDCASE.
    ENDLOOP.
*--------------------------------------------------------------------*
* Ok - alle Test sind zufriedenstellend
*--------------------------------------------------------------------*
    rv_can_be_condensed = abap_true.
  ENDMETHOD.
ENDCLASS.

Folgende Benutzer bedankten sich beim Autor black_adept für den Beitrag (Insgesamt 3):
MurdockThomas R.ewx

live long and prosper
Stefan Schmöcker

email: stefan@schmoecker.de

Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von a-dead-trousers (Top Expert / 4394 / 223 / 1182 ) »
Danke für die Aufgabe und die Lösung. Ich möchte nur noch anmerken, dass das nur mit INTEGER und NUMERIC (D, T, N) Datentypen funktioniert.

Für FLOAT, DECIMALS, PACKED und CHAR müsste man theoretisch eine binäre Darstellung verwenden um Vorgänger bzw. Nachfolger ermitteln zu können. Das hab ich aber nicht weiter verfolgt 😉
Theory is when you know something, but it doesn't work.
Practice is when something works, but you don't know why.
Programmers combine theory and practice: Nothing works and they don't know why.

ECC: 6.18
Basis: 7.50

Re: Knobelaufgabe ( Frühling 2023 )

Beitrag von Murdock (Specialist / 123 / 58 / 10 ) »
Hallo zusammen,

danke nochmal an black_adept für die Aufgabe, die den wunderbaren Nebeneffekt hat, dass man das Ergebnis auch gebrauchen kann 😉
Da er meine Lösung erwähnt hat, stelle ich sie hier mal im Original, so wie eingereicht, ein. Wenn ich etwas Zeit habe, werde ich ich dann noch mindestens seinen sehr guten Hinweis darauf, dass I EQ xxx = I BT xxx xxx ist, berücksichtigen.

Code: Alles auswählen.

CLASS zcl_range DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    CLASS-METHODS condense_range_option
      IMPORTING
        iv_option TYPE tvarv_opti
      CHANGING
        ct_range  TYPE ANY TABLE .
    "! <p class="shorttext synchronized" lang="de">Verdichten einer Select-Option Range</p>
    CLASS-METHODS condense_range
      CHANGING
        ct_range TYPE ANY TABLE .
    CLASS-METHODS init
      IMPORTING
        it_datatab TYPE any .
  PROTECTED SECTION.
  PRIVATE SECTION.
    TYPES: BEGIN OF mty_datatype,
             typekind TYPE  abap_typekind,
             length   TYPE   i,
             decimals TYPE   i,
           END OF mty_datatype.

    CLASS-METHODS: get_next_value
      IMPORTING
        iv_wert            TYPE any
        iv_datatype        TYPE abap_typekind
      EXPORTING
        VALUE(ev_naechster_wert) TYPE any,
      is_in_range
        IMPORTING
          it_range         TYPE rseloption
          iv_value         TYPE rsdsselopt-low
        RETURNING
          VALUE(rv_result) TYPE abap_bool,
      condense_range_eq
        CHANGING
          ct_range TYPE ANY TABLE,
      condense_range_bt
        CHANGING
          ct_range TYPE ANY TABLE,
      condense_range_eq_in_bt
        CHANGING
          ct_range TYPE ANY TABLE,
      get_datatype_col
        IMPORTING
          iv_spalte       TYPE name_feld
          it_datatab      TYPE ANY TABLE
        RETURNING
          VALUE(rs_dtype) TYPE mty_datatype,
      create_ref_to_data
        IMPORTING
          is_datatype    TYPE mty_datatype
        RETURNING
          VALUE(rr_dref) TYPE REF TO data,
      is_countable_type
        IMPORTING
          iv_typekind      TYPE abap_typekind
        RETURNING
          VALUE(rv_result) TYPE abap_bool,
      set_datatype_low
        IMPORTING
          is_datatype TYPE mty_datatype,
      get_datatype_low
        RETURNING
          VALUE(rs_datatype) TYPE mty_datatype.

    CONSTANTS: mc_loeschen(1) VALUE '#'.
    CLASS-DATA ms_datatype_low TYPE mty_datatype.

ENDCLASS.



CLASS zcl_range IMPLEMENTATION.


  METHOD condense_range.
    DATA(ls_datatype) = get_datatype_col( iv_spalte = 'LOW' it_datatab = ct_range ).
    set_datatype_low( is_datatype = ls_datatype ).
    " Den Kram nur machen, wenn LOW einen Datentyp hat, den man evtl. mit + 1 hochzählen kann
    IF is_countable_type( ls_datatype-typekind ).
      condense_range_option( EXPORTING iv_option = 'EQ' CHANGING ct_range = ct_range ).
      condense_range_option( EXPORTING iv_option = 'BT' CHANGING ct_range = ct_range ).
      " Schauen, ob eine EQ Zeile mittlerweile in einen BT Bereich fällt
      condense_range_option( EXPORTING iv_option = 'ET' CHANGING ct_range = ct_range ).
    ENDIF.
  ENDMETHOD.


  METHOD condense_range_bt.
    DATA: lt_range TYPE rseloption.

    lt_range = CORRESPONDING #( ct_range ).

    SORT lt_range BY sign option high low.
    DELETE ADJACENT DUPLICATES FROM lt_range.
    READ TABLE lt_range WITH KEY sign = 'I' option = 'BT' ASSIGNING FIELD-SYMBOL(<ls_aktuelle_zeile>).
    IF sy-subrc = 0.
      DATA(lv_zeilenindex) = sy-tabix.
      DATA(lv_letzte_zeile) = lines( lt_range ).
      DATA(lv_weitermachen) = abap_true.
      DATA(ls_datatype) = get_datatype_low( ).
      IF ls_datatype IS INITIAL.
        ls_datatype = get_datatype_col( iv_spalte = 'LOW' it_datatab = ct_range ).
        set_datatype_low( is_datatype = ls_datatype ).
      ENDIF.

      " Feldsymbol mit passenden Datentyp (wie -low) zum Hochzählen erstellen
      DATA(lr_dref) = create_ref_to_data( ls_datatype ).
      ASSIGN lr_dref->* TO FIELD-SYMBOL(<lv_naechster_wert>).

      WHILE lv_zeilenindex < lv_letzte_zeile AND lv_weitermachen = abap_true.
        lv_zeilenindex = lv_zeilenindex + 1.
        ASSIGN lt_range[ lv_zeilenindex ] TO FIELD-SYMBOL(<ls_naechste_zeile>).

        get_next_value( EXPORTING iv_wert = <ls_aktuelle_zeile>-high iv_datatype = ls_datatype-typekind
                        IMPORTING ev_naechster_wert = <lv_naechster_wert> ).

        " Passt der aktuelle Höchstwert in den nächsten Bereich?
        IF <ls_naechste_zeile>-sign = 'I' AND <ls_naechste_zeile>-option = 'BT'
        AND <ls_aktuelle_zeile>-high <= <ls_naechste_zeile>-high
        AND ( ( <ls_aktuelle_zeile>-high >= <ls_naechste_zeile>-low )
           OR ( <ls_naechste_zeile>-low = <lv_naechster_wert> ) ). " Bereiche genau angrenzend?

          " Nächsten Höchstwert als neuen Höchstwert übernehmen
          <ls_aktuelle_zeile>-high = <ls_naechste_zeile>-high.
          IF <ls_aktuelle_zeile>-low > <ls_naechste_zeile>-low.
            <ls_aktuelle_zeile>-low = <ls_naechste_zeile>-low.
          ENDIF.
          <ls_naechste_zeile>-option = mc_loeschen.
        ELSE.
          ASSIGN lt_range[ lv_zeilenindex ] TO <ls_aktuelle_zeile>.
        ENDIF.
      ENDWHILE.
      DELETE lt_range WHERE option = mc_loeschen.
      ct_range = CORRESPONDING #( lt_range ).
    ENDIF.
  ENDMETHOD.


  METHOD condense_range_eq.
    DATA: lt_range TYPE rseloption.

    lt_range = CORRESPONDING #( ct_range ).
    SORT lt_range BY sign option low.
    DELETE ADJACENT DUPLICATES FROM lt_range.
    READ TABLE lt_range WITH KEY sign = 'I' option = 'EQ' ASSIGNING FIELD-SYMBOL(<ls_aktuelle_zeile>).

    IF sy-subrc = 0.
      DATA(lv_zeilenindex) = sy-tabix.
      DATA(lv_letzte_zeile) = lines( lt_range ).
      DATA(lv_aktuell_max_wert) = <ls_aktuelle_zeile>-low.
      DATA(lv_weitermachen) = abap_true.
      DATA(ls_datatype) = get_datatype_low( ).

      IF ls_datatype IS INITIAL.
        ls_datatype = get_datatype_col( iv_spalte = 'LOW' it_datatab = ct_range ).
        set_datatype_low( is_datatype = ls_datatype ).
      ENDIF.

      " Feldsymbol mit passenden Datentyp (wie -low) zum Hochzählen erstellen
      DATA(lr_dref) = create_ref_to_data( ls_datatype ).
      ASSIGN lr_dref->* TO FIELD-SYMBOL(<lv_naechster_wert>).

      WHILE lv_zeilenindex < lv_letzte_zeile AND lv_weitermachen = abap_true.
        lv_zeilenindex = lv_zeilenindex + 1.
        ASSIGN lt_range[ lv_zeilenindex ] TO FIELD-SYMBOL(<ls_naechste_zeile>).

        get_next_value( EXPORTING iv_wert = lv_aktuell_max_wert iv_datatype = ls_datatype-typekind
                        IMPORTING ev_naechster_wert = <lv_naechster_wert> ).
        " Ist der folgende Wert = der aktuelle Wert + 1?
        IF <ls_naechste_zeile>-sign = 'I' AND <ls_naechste_zeile>-option = 'EQ'
           AND condense( val = |{ <lv_naechster_wert> }| )
             = condense( val = |{ <ls_naechste_zeile>-low ALPHA = OUT }| ).
          " Werte mittels Option BT zusammenfassen
          <ls_aktuelle_zeile>-option = 'BT'.
          <ls_aktuelle_zeile>-high =  <ls_naechste_zeile>-low. "CONV #( <lv_naechster_wert>  ).
          <ls_naechste_zeile>-option = mc_loeschen . " für späteres Löschen
        ELSE.
          ASSIGN lt_range[ lv_zeilenindex ] TO <ls_aktuelle_zeile>.
          " Wenn eine neue Option kommt, kann die Schleife verlassen werden
          IF <ls_naechste_zeile>-option <> 'EQ'.
            lv_weitermachen = abap_false.
          ENDIF.
        ENDIF.
        lv_aktuell_max_wert = <ls_naechste_zeile>-low.
      ENDWHILE.

      DELETE lt_range WHERE option = mc_loeschen.
      ct_range = CORRESPONDING #( lt_range ).
    ENDIF.
  ENDMETHOD.


  METHOD condense_range_eq_in_bt.
    " Löschen der EQ Werte, die in einem BT Bereich sind
    DATA: lt_range TYPE rseloption.

    lt_range = CORRESPONDING #( ct_range ).
    SORT lt_range BY sign option low.
    DELETE ADJACENT DUPLICATES FROM lt_range.
    READ TABLE lt_range WITH KEY sign = 'I' option = 'EQ' ASSIGNING FIELD-SYMBOL(<ls_aktuelle_zeile>).
    IF sy-subrc = 0.
      DATA(lv_zeilenindex) = sy-tabix.
      DATA(lv_letzte_zeile) = lines( lt_range ).
      DATA(lv_weitermachen) = abap_true.

      WHILE lv_zeilenindex <= lv_letzte_zeile AND lv_weitermachen = abap_true.
        IF is_in_range( it_range = lt_range iv_value = <ls_aktuelle_zeile>-low ).
          <ls_aktuelle_zeile>-option = mc_loeschen.
        ENDIF.
        lv_zeilenindex = lv_zeilenindex + 1.
        ASSIGN lt_range[ lv_zeilenindex ] TO <ls_aktuelle_zeile>.
        " Wenn eine neue Option kommt, kann die Schleife verlassen werden
        IF <ls_aktuelle_zeile>-option <> 'EQ'.
          lv_weitermachen = abap_false.
        ENDIF.
      ENDWHILE.
      DELETE lt_range WHERE option = mc_loeschen.
      ct_range = CORRESPONDING #( lt_range ).
    ENDIF.
  ENDMETHOD.


  METHOD condense_range_option.
    CASE iv_option.
      WHEN 'EQ'.
        condense_range_eq( CHANGING ct_range = ct_range ).
      WHEN 'BT'.
        condense_range_bt( CHANGING ct_range = ct_range ).
      WHEN 'ET'.
        condense_range_eq_in_bt( CHANGING ct_range = ct_range ).
    ENDCASE.
  ENDMETHOD.


  METHOD create_ref_to_data.
    IF is_datatype-typekind = cl_abap_typedescr=>typekind_date.
      CREATE DATA rr_dref TYPE (is_datatype-typekind).
    ELSE.
      CREATE DATA rr_dref TYPE (is_datatype-typekind) LENGTH is_datatype-length.
    ENDIF.
  ENDMETHOD.


  METHOD get_datatype_col.
    DATA: lr_datatab  TYPE REF TO data.
    FIELD-SYMBOLS: <lt_datatab> TYPE table.

    GET REFERENCE OF it_datatab INTO lr_datatab.
    ASSIGN lr_datatab->* TO <lt_datatab>.
    ASSIGN COMPONENT iv_spalte OF STRUCTURE <lt_datatab>[ 1 ] TO FIELD-SYMBOL(<ls_field>).
    IF <ls_field> IS ASSIGNED.
      DATA(lr_type_descr) =  cl_abap_typedescr=>describe_by_data( <ls_field> ).
      rs_dtype = VALUE #( typekind  = lr_type_descr->type_kind
                          length    = CAST cl_abap_elemdescr( lr_type_descr )->output_length
                          decimals  = lr_type_descr->decimals )  .
    ENDIF.
  ENDMETHOD.


  METHOD get_datatype_low.
    rs_datatype = ms_datatype_low.
  ENDMETHOD.


  METHOD get_next_value.
    TRY.
    CASE iv_datatype.
      WHEN cl_abap_typedescr=>typekind_date.
        ev_naechster_wert = CONV dats( iv_wert ) + 1.
      WHEN OTHERS.
        ev_naechster_wert = iv_wert + 1 .
    ENDCASE.
    CATCH CX_SY_CONVERSION_NO_NUMBER.
      ev_naechster_wert = iv_wert.
    ENDTRY.
  ENDMETHOD.


  METHOD init.
    " Für evtl. direkten Aufruf der condense_range_option Methode (nicht über condense_range() ), z.B. Unittest
    DATA(ls_datatype) = get_datatype_col( iv_spalte = 'LOW' it_datatab = it_datatab ).
    set_datatype_low( is_datatype = ls_datatype ).
  ENDMETHOD.


  METHOD is_countable_type.
    IF iv_typekind = cl_abap_typedescr=>typekind_date OR
       iv_typekind = cl_abap_typedescr=>typekind_char OR
       iv_typekind = cl_abap_typedescr=>typekind_clike OR
       iv_typekind = cl_abap_typedescr=>typekind_csequence OR
       iv_typekind = cl_abap_typedescr=>typekind_int  OR
       iv_typekind = cl_abap_typedescr=>typekind_int1 OR
       iv_typekind = cl_abap_typedescr=>typekind_int8 OR
       iv_typekind = cl_abap_typedescr=>typekind_int2 OR
       iv_typekind = cl_abap_typedescr=>typekind_num  OR
       iv_typekind = cl_abap_typedescr=>typekind_numeric OR
       iv_typekind = cl_abap_typedescr=>typekind_string.
      rv_result = abap_true.
    ENDIF.
  ENDMETHOD.


  METHOD is_in_range.
    LOOP AT it_range ASSIGNING FIELD-SYMBOL(<ls_range>) WHERE low <= iv_value AND high >= iv_value
          AND sign = 'I' AND option = 'BT'.
      rv_result = abap_true.
      RETURN.
    ENDLOOP.

  ENDMETHOD.

  METHOD set_datatype_low.
    ms_datatype_low  = is_datatype.
  ENDMETHOD.
ENDCLASS.

Folgende Benutzer bedankten sich beim Autor Murdock für den Beitrag:
ewx


Seite 1 von 1

Vergleichbare Themen

3
Antw.
1414
Views
Knobelaufgabe ( Sommer 2023 ) - Robuster Programmablauf
von black_adept » 26.06.2023 12:51 • Verfasst in SAP - Allgemeines
4
Antw.
1351
Views
Knobelaufgabe
von black_adept » 26.03.2021 13:55 • Verfasst in SAP - Allgemeines
22
Antw.
4246
Views
Knobelaufgabe - Advent 2021
von black_adept » 29.11.2021 12:58 • Verfasst in SAP - Allgemeines
5
Antw.
2693
Views
Knobelaufgabe ( Oktober 2021 )
von black_adept » 19.10.2021 12:19 • Verfasst in SAP - Allgemeines
6
Antw.
2792
Views
Knobelaufgabe ( August 2021 )
von black_adept » 13.08.2021 14:17 • Verfasst in SAP - Allgemeines

Newsletter Anmeldung

Keine Beiträge verpassen! Wöchentlich versenden wir lesenwerte Beiträge aus unserer Community.
Die letzte Ausgabe findest du hier.
Details zum Versandverfahren und zu Ihren Widerrufsmöglichkeiten findest du in unserer Datenschutzerklärung.