Iterators

Updated May 2018

To access elements from a collection without knowing the specific keys, an "iterator" is used. Iterators are special variables which receive the value of the next element of a collection each time through the loop.

To iterate through a collection, a FOREACH loop is used:

foreach $$i in $aryname({startkey})   ! fwd iterator

    print $$i                         ! print the element value

    print .key($$i)                   ! print the element key

next $$i

foreach’reverse $$j in $aryname()     ! reverse iterator

    print $$j                         ! print the element value

    print .key($$j)                   ! print the element key

next $$j

 

The optional startkey may be any kind of expression that could appear on the right side of an assignment statement, i.e. a literal, a variable, a variable formatted with USING, a function call, etc. If the expression is numeric, the value is converted to string format (truncating a fractional value to integer). See History below for a revision to the startkey handling. Note that the startkey only applies to the forward iterator, and only has an effect if there is a exact match in the map. This is not like an ISAM lookup, where a failed initial key lookup will nevertheless affect the subsequent next operation.

Note: the above examples assume varstr elements; for varx elements, you wouldn’t try to print the $$i or $$j iterator values directly; instead you would assign them to a structure and then access the members, i.e.

    Prodstruct = $$i                 ! copy iterator value to structure

    print Prodstruct.field           ! print a field of the element value

    print .key($$j)                  ! print the element key

 

Although this is similar to a standard for/next loop, there are some noteworthy differences:

•   First, unlike the standard for/next loop where the loop variable must have been previously defined, in the foreach loop, the iterator variable ($$i in the example above) is created on the fly and exists just for the duration of the loop. Iterator variables must all start with $$ (which should not be used for any other variable type.) Since it is destroyed at the end of the loop, the same iterator variable name can be used in other foreach loops.

•   Unlike the standard for/next loop, here there is no explicit ending value. The starting value is the first item in the collection (unless the optional startkey parameter is specified.) The concept is similar to sequencing through an indexed file until the end, except in this case if the specified startkey doesn’t exist, the entire sequence will be empty and the loop will be skipped.) To omit the startkey, use empty parens, e.g. $aryname().

•   The order of the sequence will be according to the key. You can specify a reverse sequence by using the keyword foreach’reverse instead of foreach. Note however that for the reverse iterator, the startkey option is ignored; it always starts at the highest key in the collection.

Adding, deleting or modifying keys while iterating will render the iterator unpredictable, and thus should be strictly avoided. You may, however, modify the values of the elements as you iterate. Prior to 6.5.1633.0 (see History below), the iterator itself was read-only, so in order to change the value, you would have had to access it via the .key() function. As of 6.5.1633.0, you can just modify the iterator directly.  For example, the loop below shows two methods of modifying the value of each element by prefixing it with "Beautiful " and folding the existing preexisting part to upper case (assuming varstr elements):

foreach $$i in $capitals() ! fwd iterator

    $capitals(.key($$i)) = "Beautiful " + ucs($$i)    ! method 1 (works in all versions)

! or...

    $$i = "Beautiful " + ucs($$i)                     ! method 2 (6.5.1633.0+)

next $$i

 

Within the loop, the iterator variable (e.g. $$i) will contain the value of the current element in the sequence. This is a read-only variable! And its type matches the element type of the ordered map (string for varstr, unformatted for varx).You can also access the key of the current element using the special "dot function" .key(<iterator>) as in the example foreach loop above. To modify an element's value, use the .key(<iterator>) function as the key. For example, the loop below would prefix each element with "Beautiful " and fold the capital name to upper case (assuming varstr elements):

foreach $$i in $capitals() ! fwd iterator

    $capitals(.key($$i)) = "Beautiful " + ucs($$i)

next $$i

 

In the following example, we copy a portion of the ordered map $cap1 (i.e. the capitals of the States starting with "C") to a new ordered map $cap2. Note that we can't take advantage of startkey to jump directly to the C's because it only works with a when there is an exact match.

dimx $cap1, ordmap(varstr;varstr)

dimx $cap2, ordmap(varstr;varstr)

map1 state$,s,40

...

foreach $$i in $cap1()

    state$ = .key($$i)

    if state$[1,1] = "C" then

        $cap2(state$) = $$i

        repeat

    elseif state$ >= "D" then

        exit

    endif

next $$i

 

Note that in the above example, we didn't have to introduce the temporary variable state$, but doing so probably increases efficiency by eliminating redundant use of the .key($$i) operation. Ordered map operations are extremely efficient given what they involve, but they are still considerably more costly than ordinary variable or array references, particularly as the map gets large. Also note that as with other control loops, the control statements EXIT (as well as REPEAT) are available.

History

2018 May, A-Shell 6.5.1636, compiler edit 859:  the starting key in a FOREACH statement may now be any kind of expression. Previously, it only allowed a simple variable or a literal string or numeric constant; numeric literals were allowed but weren't converted to string and thus typically failed to match any items in the map.

2017 March, A-Shell 6.5.1633:  Iterators made to be writable; previously they were read-only.