loopsconditional-statementsabapuntil-loop

Checking all rows of itab against a condition via REDUCE?


In order to check whether all entries of an internal table lt_itab meet a condition COND, I would like to use REDUCE statement. The loop of course needs to terminate once a line violating COND occurs. The second code block further down seems to work but appears to me like a slight abuse of the iteration index. Are you aware of a better/more transparent solution within the REDUCE syntax? Is it possible to work with a structure (integer, boolean) for the iteration variable? The INDEX INTO option seems not to work with REDUCE. Compatibility to kernel-release 753 (or lower) would be nice.

Here is my Minimal Reproducible Example (MRE) which passes syntax check only if lvr_flag_allowed = abap_false OR is commented out (viz -> "lvr_flag_allowed = abap_false OR):

DATA: lt_itab         TYPE TABLE OF i,
      rv_flag_allowed TYPE boole_d.

lt_itab = VALUE #( ( 2 ) ( 1 ) ( -1 ) ( 5 ) ).

IF lt_itab IS NOT INITIAL.
  rv_flag_allowed = REDUCE #( INIT lvr_flag_allowed = abap_true
                              FOR lvf_idx = 1 UNTIL lvr_flag_allowed =  abap_false OR
                                                    lvf_idx > lines( lt_itab )
                              NEXT lvr_flag_allowed = xsdbool( lt_itab[ lvf_idx ] < 0 ) ).
ENDIF.

RETURN.

Currently it gives this syntax check message (its ID is MESSAGEG[M):

The variable "LVR_FLAG_ALLOWED" cannot be used here.

Do you know the technical reason why this does not work? The SAP documentation on REDUCE - Reduction Operator only states

Usually the expression expr (after THEN) and the termination condition log_exp (after UNTIL or WHILE) depend on the iteration variable var.

Hence a workaround MRE came to my mind while writing this down:

DATA: lt_itab       TYPE TABLE OF i,
*      rv_flag_allowed TYPE boole_d,
      rv_last_index TYPE i.

lt_itab = VALUE #( ( 2 ) ( 1 ) ( -22 ) ( 5 ) ( 7 ) ( 4 ) ).

IF lt_itab IS NOT INITIAL.
  rv_last_index = REDUCE #( INIT lvr_last_index = 0
                              FOR lvf_idx = 1 THEN COND #( WHEN lt_itab[ lvf_idx ] < 0
                                                                THEN 0
                                                                ELSE lvf_idx + 1 )
                                              UNTIL lvf_idx = 0 OR
                                                    lvf_idx > lines( lt_itab )
                              NEXT lvr_last_index = lvr_last_index + 1 ).
ENDIF.

RETURN.

It makes "double" use of the iteration index and returns rv_last_index = 3. I'm returning an integer now rather than a boolean in order to check the correct abort result. Does this seem correct to you?

Many thanks in advance for your feedback and improvement suggestions (beyond the classical while/until loops ;-)) !


Solution

  • I would actually like to express this using line_exists, unfortunately table expressions only support equality comparisons though (and sadly not <):

    "                                              invalid syntax v
    DATA(some_negative) = xsdbool( line_exists( values[ table_line < 0 ] ) ).
    

    A slightly more verbose but working variant would be using a LOOP AT with an immediate EXIT. (yes, this is not "modern syntax", though still very readable IMO):

    DATA(some_negative) = abap_false.
    LOOP AT values WHERE table_line < 0 TRANSPORTING NO FIELDS.
      some_negative = abap_true.
      EXIT.
    ENDLOOP.
    

    I don't think that REDUCE is the right tool for the job, as it is supposed to comprehend the table into one value (in other languages there is also no short-circuit, e.g. the .reduce in JS, though they have other methods for this purpose like .some and .every and the alike). If the number of truthy rows is low, not having short circuiting for them might be acceptable, and this REDUCE statement would at least not visit falsy rows through the additional WHERE ( ... ) clause:

    DATA(some_negative) = REDUCE abap_bool(
      INIT result = abap_false
      FOR entry IN values
       WHERE ( table_line < 0 )
       NEXT result = abap_true
    ).